汉诺塔问题及Java代码实现(牛客笔记)

汉诺塔问题

描述

相传在古印度圣庙中,有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘(如图1)。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。(图片来自百度百科)

递归思想

将汉诺塔问题的解决方案可以分为3步:

  1. 把n-1个盘子从Left借助Mid,搬到Right柱子上
  2. 把剩下的底层盘子从Left移动到Right柱子上
  3. 把n-1个盘子从mid借助Left移动到Right柱子上

示意图如下:

至于如何把n-1个盘子搬到另一个柱子上,同样参照上面的3步,只不过它整体的变为从Left借助Right移动到Mid(其实就是要搞清楚是从哪里开始,借助哪个柱子,最后到哪个柱子,整体是这样,每一个小分步也是这样的思想,这也就是递归思想)。

具体代码如下:

import java.util.*;

class Solution {
    ArrayList<String> result = new ArrayList<String>();//移动结果存入result列表
    public void hanoi(String left,String mid,String right,int n){//参数分别代表(起始位置,借助位置,终点位置,需要移动的个数)
        if(n==0) return;//如果n=0 返回
        hanoi(left,right,mid,n-1);//1.把n-1个盘子从left(第一个参数)借助right(第三个参数),移动到mid(第二个参数)
        String t = "move from "+left+" to "+right;//2.把剩下的一个从left(第一个参数)移动到right(第三个参数)
        result.add(t);
        hanoi(mid,left,right,n-1);//3.把中间n-1个盘子,从mid(第一个参数)借助right(第三个参数),移动到right(第二个参数)
    }

    public ArrayList<String> getSolution(int n) {
        // write code here
        hanoi("left","mid","right",n);//函数调用
        return result;
    }
}
public class Main{
    public static void main(String[] args) {
        ArrayList<String> result = new ArrayList<String>();
        Scanner scan = new Scanner(System.in);
        int n = Integer.parseInt(scan.nextLine());
        Solution s = new Solution();
        result = s.getSolution(n);
        for (String ss:result) {
            System.out.println(ss);
        }
    }
}

时间复杂度:O(2^N)。假设把n个盘子搬动耗时T(n),则有T(n)=2T(n-1)+1,从而[T(n)+1]/[T(n-1)+1]=2,对该等比数列求解即可。
空间复杂度:O(N)。递归栈的深度。

非递归思想

(此方法来自牛客)移动过程分为4步:

  1. 将当前柱子顶部的盘子,移动到顺时针的下一个柱子
  2. 将当前柱子和剩余的另一根柱子比较,将其中的一根柱子的盘子移动到另一根柱子上(很显然,这种移动方式是固定的)
  3. 指针指向顺时针的下一根柱子。
  4. 遇到某一根柱子的盘子满了时,停止循环;否则回到步骤1。

注意点在于当n为奇数时,顺时针的柱子顺序为left,right,mid;当n为偶数时,顺时针的柱子顺序为left,mid,right。

以n=3为例,示意图如下:

 具体代码如下:

import java.util.*;

/***
 * 非递归汉诺塔
 */
class Pillar{ //柱子类
    private String name;
    private LinkedList<Integer> stack;
    Pillar(){
        this.stack = new LinkedList<>();
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public LinkedList<Integer> getStack() {
        return stack;
    }

    public void setStack(LinkedList<Integer> stack) {
        this.stack = stack;
    }
}
class Solve{
    LinkedList<String> result = new LinkedList<String>();
    public void initPillar(Pillar a,Pillar b,Pillar c,int n){//初始化顺序
        for (int i = n; i > 0; i--) {
            a.getStack().push(i);
        }
        a.setName("left");
        if(n % 2 ==1){
            b.setName("right");
            c.setName("mid");
        }else{
            b.setName("mid");
            c.setName("right");
        }
    }

    public void move(Pillar a,Pillar b){ //1.将a顶层盘子移入b柱子中
        result.push("move from "+a.getName()+" to "+b.getName());
        b.getStack().push(a.getStack().peek());
        a.getStack().pop();
    }
    public void moveOneToOne(Pillar a,Pillar b){ //2.对于输入的两个柱子,将其中一个柱子的顶层盘子移到另一个柱子中
        if(a.getStack().isEmpty()){//如果a是空蛮自然只能把b移动到a
            move(b,a);
        }else if(b.getStack().isEmpty()){//如果b是空蛮自然只能把a移动到b
            move(a,b);
        }else if(a.getStack().peek() > b.getStack().peek()){//如果a柱子顶层盘子大于b顶层盘子,只能把b->a
            move(b,a);
        }else {
            move(a, b);
        }
    }

    public boolean isEnd(Pillar b,Pillar c,int n){ //若有一个柱子满了,则循环结束
        if(b.getStack().size() == n||c.getStack().size()==n){
            return true;
        }else{
            return false;
        }
    }

    void hanoi(int n,Pillar A,Pillar B,Pillar C){
        for (int i = 0; !isEnd(B,C,n); i++) {
            if(i % 3 ==0){
                move(A,B);
                if(isEnd(B,C,n)){
                    break;
                }
                moveOneToOne(A,C);
            }else if(i % 3 ==1){
                move(B,C);
                if(isEnd(B,C,n)){
                    break;
                }
                moveOneToOne(B,A);
            }
            else{
                move(C,A);
                if(isEnd(B,C,n)){
                    break;
                }
                moveOneToOne(C,B);
            }
        }
    }
}
public class Main{
    LinkedList result = new LinkedList();
    public static void main(String[] args) {
        Pillar a = new Pillar();//left
        Pillar b = new Pillar();//mid
        Pillar c = new Pillar();//right
        Scanner scan = new Scanner(System.in);
        int n = Integer.parseInt(scan.nextLine());
        Solve s = new Solve();
        s.initPillar(a,b,c,n);
        s.hanoi(n,a,b,c);
        for (String ss: s.result) {
            System.out.println(ss);
        }
    }
}

时间复杂度:O(2^N),因为总的移动次数没有减少,与方法一相比时间复杂度不会降低。
空间复杂度:O(N),开辟了3个数组用来存放盘子编号。

  • 3
    点赞
  • 9
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:深蓝海洋 设计师:CSDN官方博客 返回首页
评论

打赏作者

杳远

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值