【算法笔记】使用栈实现汉诺塔(Hanoi)经典算法

北慕城南 提交于 2019-12-03 03:16:19

汉诺塔(Hanoi)算法,应该是每一个程序员都会学习到的递推算法之一,汉诺塔是一个很著名的智力题,但是这里就不科普它的由来了,我们直接进入正题。
这里写图片描述
      如上图,假设A棒有五个原盘,依次移动,每次移动一块,小的永远只能在上面,最终移动到C棒上,如何用算法实现呢?
      从这里移动的逻辑我们很容易发现,A帮不就像一个栈吗,栈顶必须先出,网上看过很多汉诺塔算法,很少涉及到用栈实现,的确,算法拿出来了,用什么都一样,在我学习的时候,教材上是用的char,直接模拟推算,没用真正移动数据实现真正的Hanoi思想,所以,琢磨了一会,写了一个用栈实现的算法。
      首先,既然是栈,为了方便跟踪,写了一个自己的MyStack包装了一下Java的Stack,贴上代码:

class MyStack{
    private String name;
    private Stack<Integer> data;
    public MyStack(String name){
        this.name=name;
        data=new Stack<>();
    }
    public String getName(){
        return this.name;
    }
    public void push(int data){
        if(!this.data.isEmpty()&&this.data.peek()<data){
            System.out.println("出错");
        }
        this.data.push(data);
    }
    public int peek(){
        return data.peek();
    }
    public int pop(){
        return data.pop();
    }
    public int size(){
        return data.size();
    }
    public boolean isEmpty(){
        return data.isEmpty();
    }
}

      然后就是Hanoi递推的实现,还是贴上图片
这里写图片描述

      我们要按照Hanoi的逻辑将原盘从A棒移动到C棒,那么,就必须以B棒作为媒介,
最大的必须在最下面,所以,我们必须把上面4个圆盘先移动到B棒上。
      但是又看,上面4个,要想把第四个移动到B棒,就得用C棒暂时当媒介,先把上面3个移动到C棒,以此类推,知道只有最后一个的时候,就可以直接移动了,所以,我们要做的就是想出一个算法,递推到只剩一个圆盘,然后慢慢回栈,到第二,第三,第四,最后第五。
先贴上代码:

public static void hanoi(int size,MyStack a,MyStack b,MyStack c){
        if(size==1){
            c.push(a.pop());
        }else{
            int n=b.size();
            hanoi(size-1,a,c,b);
            c.push(a.pop());
            hanoi(b.size()-n,b,a,c);
        }
    }

      首先解释参数中的size,因为栈无法在不取出元素的情况下递减长度,所以增加了参数size作为栈的圆盘指针,限定只能移动size个圆盘。

      所以,当size==1的时候,就是只剩下一个圆盘,那么就顺理成章的直接移动到C棒了,不必在意此时的C棒回栈后是B棒还是A棒,那不是此时递归该担心的事情。

      在算法中,如果size!=1,那么说明我们需要一个作为媒介,让size-1个圆盘先暂时放到媒介上,然后将第size个圆盘放过去,所以,我们需要进行一次递归,将第size-1个上面的圆盘重新进行计算该存放的位置,计算完成后,然后放入第size个圆盘到C帮,然后再将媒介B棒中的圆盘又以A棒为媒介,以此方式放入C盘。

      至于为什么要在递归前缓存一次B棒的size,因为进入递归前不知道B棒是否有数据,说不定此次计算正是上一次的递归呢,不知道后面的方法B棒会是怎样的存在,不知道会进入多少次递归,假设B棒在进入第一次递归前长度为2,递归完后,长度为5,第二次递归时,如果不限制size长度,直接使用B棒的size,那么,除了第一次递归时增加的3个数据,还会把原本的2个数据一起计算进去,博主就在这个坑绕了一些时间,最后跟踪了一下才明白这个。

文字有点多,最后贴上完整代码:

public class Hanoi {
    private static int m=1;
    private static MyStack a=new MyStack("A");
    private static MyStack b=new MyStack("B");
    private static MyStack c=new MyStack("C");  
    public static void main(String[] args) {
        for(int i=5;i>0;i--){
            a.push(i);
        }
        hanoi(a.size(),a,b,c);
        print(c);
    }
    public static void hanoi(int size,MyStack a,MyStack b,MyStack c){
        if(size==1){
            System.out.println("第"+m+++"步,从"+a.getName()+"移动了  "+a.peek()+"到了"+c.getName());
            c.push(a.pop());
        }else{
            int n=b.size();
            hanoi(size-1,a,c,b);
            System.out.println("第"+m+++"步,从"+a.getName()+"移动了  "+a.peek()+"到了"+c.getName());
            c.push(a.pop());
            hanoi(b.size()-n,b,a,c);
        }
    }

    public static void print(MyStack temp){
        System.out.println("size:"+temp.size());
        for(int i=0,n=temp.size();i<n;i++){
            System.out.print(temp.pop()+" ");
        }
        System.out.println();
    }
}
class MyStack{
    private String name;
    private Stack<Integer> data;
    public MyStack(String name){
        this.name=name;
        data=new Stack<>();
    }
    public String getName(){
        return this.name;
    }
    public void push(int data){
        if(!this.data.isEmpty()&&this.data.peek()<data){
            System.out.println("出错");
        }
        this.data.push(data);
    }
    public int peek(){
        return data.peek();
    }
    public int pop(){
        return data.pop();
    }
    public int size(){
        return data.size();
    }
    public boolean isEmpty(){
        return data.isEmpty();
    }
}

核心算法只有那一段,其他是我为了方便跟踪增加的,各位在测试的时候可以选择性删除。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!