目录

1 问题描述

2 解决方案 

2.1 递归法

2.2 非递归法


1 问题描述

Simulate the movement of the Towers of Hanoi Puzzle; Bonus is possible for using animation.

e.g. if n = 2 ; A→B ; A→C ; B→C;

if n = 3; A→C ; A→B ; C→B ; A→C ; B→A ; B→C ; A→C;

翻译:模拟汉诺塔问题的移动规则;获得奖励的移动方法还是有可能的。

相关经典题目延伸:

引用自百度百科:

有三根相邻的柱子,标号为A,B,C,A柱子上从下到上按金字塔状叠放着n个不同大小的圆盘,要把所有盘子一个一个移动到柱子C上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方,请问至少需要多少次移动,设移动次数为H(n)。

首先我们肯定是把上面n-1个盘子移动到柱子B上,然后把最大的一块放在C上,最后把B上的所有盘子移动到C上,由此我们得出表达式:

H⑴ = 1          A—>C

H(2) = 3         A—>B;A—>C;B—>C

H(3) = 7         ...

H(4) = 15

... ...

H(n) = 2*H(n-1)+1 (n>1)

那么我们很快就能得到H(n)的一般式:

H(n) = 2^n - 1 (n>0)


2 解决方案

2.1 递归法

 import java.util.Scanner;

 public class Hanoi {

     //使用递归法求解含有n个不同大小盘子的汉诺塔移动路径,参数n为盘子数,把A塔上盘子全部移动到C塔上,B为过渡塔
public static void recursionHanoi(int n,char A,char B,char C){
if(n == 1){
System.out.print(A+"——>"+C+"\n");
}
else{
recursionHanoi(n-1,A,C,B); //使用递归先把A塔最上面的n-1个盘子移动到B塔上,C为过渡塔
System.out.print(A+"——>"+C+"\n"); //把A塔中底下最大的圆盘,移动到C塔上
recursionHanoi(n-1,B,A,C); //使用递归把B塔上n-1个盘子移动到C塔上,A为过渡塔
}
} public static void main(String[] args){
System.out.println("请输入盘子总数n:");
Scanner in = new Scanner(System.in);
int n = in.nextInt();
recursionHanoi(n,'A','B','C');
}
}

运行结果:

请输入盘子总数n:
2
A——>B
A——>C
B——>C
请输入盘子总数n:
3
A——>C
A——>B
C——>B
A——>C
B——>A
B——>C
A——>C

2.2 非递归法

要使用非递归方法,首先要解决的核心问题就是找到汉诺塔的移动规律。现在问题是把n个盘子从A塔全部移动到C塔,那么先看看n = 2,3,4时,其具体盘子的移动路径结果(PS:移动路径前标号是指A塔上原有的第几个盘子,如(1)代表A塔上原有最上面的那个盘子,依次类推...):

n = 2:

1)A—>B

2)A—>C

1)B—>C

n = 3:

1)A——>C

(2)A——>B

1)C——>B

3)A——>C

1)B——>A

(2)B——>C

1)A——>C

n = 4:

1)A——>B

2)A——>C

1)B——>C

(3)A——>B

1)C——>A

2)C——>B

1)A——>B

4)A——>C

1)B——>C

2)B——>A

1)C——>A

(3)B——>C

1)A——>B

2)A——>C

1)B——>C

从上我们可以发现,n为偶数2,4时路径前标号为(1)的盘子移动路径依次为A——>B——>C,A——>B——>C——>A——>B——>C。n为偶数2,4时路径前标号为(2)的盘子移动路径依次为A—>C,A—>C——>B——>A—>C。而且发现n = 4其标号为(1)和标号为(3)的移动路径一模一样。n为奇数3时路径前标号为(1)和(2)的盘子移动路径依次为A——>C——>B——>A——>C,A——>B——>C。

看到这里,我们可以大胆猜测盘子的具体移动路径与盘子的总个数的奇偶性以及盘子标号的奇偶性有关,而且移动的路径是固定又循环的。

那么现在设定一个二维数组用来存放盘子下次移动的塔:

char next = new char[2][3];

二维数组中行char[0]代表数组下标为偶数的盘子下次要移动的塔

二维数组中行char[1]代表数组下标为奇数的盘子下次要移动的塔

二维数组重列char[0][0]代表盘子现在在A塔准备进行下次移动

二维数组重列char[0][1]代表盘子现在在B塔准备进行下次移动

二维数组重列char[0][2]代表盘子现在在C塔准备进行下次移动

那么下面我们就来根据盘子现在所在塔,设定其下次移动的目的塔(PS:设共有n的盘子):

if(n为偶数)

{

   //数组下标为偶数的盘子移动目的塔,注意上面示例的标号为(1),其数组下标为0

   next[0][0] = ‘B’;   //看n = 4的移动路径中(1)A——>B

   next[0][1] = ‘C’;   //看n = 4的移动路径中(1)B——>C

   next[0][2] = ‘A’;   //看n = 4的移动路径中(1)C——>A

   //数组下标为奇数的盘子移动目的塔

   next[1][0] = ‘C’;   //看n = 4的移动路径中(2)A——>C

   next[1][1] = ‘A’;   //看n = 4的移动路径中(2)B——>A

   next[1][0] = ‘B’;   //看n = 4的移动路径中(2)C——>B

}

If(n为奇数)

{

   //数组下标为偶数的盘子移动目的塔,注意上面示例的标号为(1),其数组下标为0

   Next[0][0] = ‘C’;   //看n = 3的移动路径中(1)A——>C

   Next[0][1] = ‘A’;   //看n = 3的移动路径中(1)B——>A

   Next[0][2] = ‘B’;   //看n = 3的移动路径中(1)C——>B

   //数组下标为奇数的盘子移动目的塔

   Next[1][0] = ‘B’;   //看n = 3的移动路径中(2)A——>B

   Next[1][1] = ‘C’;   //看n = 3的移动路径中(2)B——>C

   Next[1][2] = ‘A’;   //此处根据观察规律假设的

}

到这里,距离使用非递归法解决汉诺塔问题已经有头绪了,此处还有注意一点就是H(n) = 2^n - 1 (n>0),即移动n个盘子需要总次数为2^n - 1 ,即使用非递归法是需要进行循环2^n - 1 次。

 package com.liuzhen.ex2;

 import java.util.Scanner;

 public class Hanoi {

     //使用递归法求解含有n个不同大小盘子的汉诺塔移动路径,参数n为盘子数,把A塔上盘子全部移动到C塔上,B为过渡塔
public static void recursionHanoi(int n,char A,char B,char C){
if(n == 1){
System.out.print(A+"——>"+C+"\n");
}
else{
recursionHanoi(n-1,A,C,B); //使用递归先把A塔最上面的n-1个盘子移动到B塔上,C为过渡塔
System.out.print(A+"——>"+C+"\n"); //把A塔中底下最大的圆盘,移动到C塔上
recursionHanoi(n-1,B,A,C); //使用递归把B塔上n-1个盘子移动到C塔上,A为过渡塔
}
} public static void noRecursionHanoi(int n){
if(n<=0){
throw new IllegalArgumentException("n must be >=1");
}
char[] hanoiPlate = new char[n]; //记录n个盘子所在的汉诺塔(hanoiPlate[1]='A'意味着第二个盘子现在在A上)
char[][] next = new char [2][3]; //盘子下次会移动到的盘子的可能性分类
int[] index = new int[n]; //根据奇偶性将盘子分为两类
for(int i=0;i<n;i=i+2){
index[i]=0;
}
for(int i=1;i<n;i=i+2){
index[i]=1;
} //一开始所有盘子都在A上
for(int i=0;i<n;i++){
hanoiPlate[i]='A';
} //n的奇偶性对移动方式的影响
if(n%2==0){
//数组下标为偶数的盘子移动目的塔,注意上面示例的标号为(1),其数组下标为0
next[0][0]='B';
next[0][1]='C';
next[0][2]='A';
//数组下标为奇数的盘子移动目的塔
next[1][0]='C';
next[1][1]='A';
next[1][2]='B';
}
else
{
//数组下标为偶数的盘子移动目的塔,注意上面示例的标号为(1),其数组下标为0
next[0][0]='C';
next[0][1]='A';
next[0][2]='B';
//数组下标为奇数的盘子移动目的塔
next[1][0]='B';
next[1][1]='C';
next[1][2]='A';
} //开始移动
for(int i=1;i<(1<<n);i++){ //总共要执行2^n-1(1<<n-1)步移动
int m=0; //m代表第m块盘子hanoiPlate[m] //根据步骤数i来判断移动哪块盘子以及如何移动
for(int j=i;j>0;j=j/2){
if(j%2!=0){ //此步骤光看代码代码有点抽象,建议手动写一下n = 2时的具体移动路径的j、m值变化
System.out.println("("+(m+1)+")"+hanoiPlate[m]+"->"+next[index[m]][hanoiPlate[m]-'A']);
hanoiPlate[m]=next[index[m]][hanoiPlate[m]-'A'];
break; //移动盘子后则退出这层循环
}
m++;
}
}
} public static void main(String[] args){
System.out.println("请输入盘子总数n:");
Scanner in = new Scanner(System.in);
int n = in.nextInt();
recursionHanoi(n,'A','B','C');
System.out.println("非递归法结果:");
noRecursionHanoi(n);
System.out.println();
}
}

运行结果:

请输入盘子总数n:
2
A——>B
A——>C
B——>C
非递归法结果:
(1)A->B
(2)A->C
(1)B->C 请输入盘子总数n:
3
A——>C
A——>B
C——>B
A——>C
B——>A
B——>C
A——>C
非递归法结果:
(1)A->C
(2)A->B
(1)C->B
(3)A->C
(1)B->A
(2)B->C
(1)A->C

 参考资料:

   1. (原创)Hanoi塔问题的递归方法与非递归方法(java实现)

算法笔记_013:汉诺塔问题(Java递归法和非递归法)的更多相关文章

  1. C语言之算法初步(汉诺塔--递归算法)

    个人觉得汉诺塔这个递归算法比电子老鼠的难了一些,不过一旦理解了也还是可以的,其实网上也有很多代码,可以直接参考.记得大一开始时就做过汉诺塔的习题,但是那时代码写得很长很长,也是不理解递归的结果.现在想 ...

  2. JavaScript算法实现之汉诺塔(Hanoi)

    目前前端新手,看到的不喜勿喷,还望大神指教. 随着Node.js,Angular.js,JQuery的流行,点燃了我学习JavaScript的热情!以后打算每天早上跟晚上抽2小时左右时间将经典的算法都 ...

  3. T2485 汉诺塔升级版(普及)(递归)

    https://www.luogu.org/problem/show?pid=T2485 题目背景 汉诺塔升级了 题目描述 现在我们有N个圆盘和N个柱子,每个圆盘大小都不一样,大的圆盘不能放在小的圆盘 ...

  4. 汉诺塔问题java实现

    问题描述 三个柱子,起初有若干个按大小关系顺序安放的盘子,需要全部移动到另外一个柱子上.移动规则:在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘. 解题思路 使用递归算法进行处理,实在理不 ...

  5. 汉诺塔问题其实很简单 Python 递归经典面试题

    话不多说,上代码 1 def hanoi_move(n, source, dest, intermediate): 2 if n >= 1: # 递归出口,只剩一个盘子 3 hanoi_move ...

  6. 【C语言】汉诺塔问题

    之前遇见这个问题,非常费劲地理解了,并写出代码,然后过段时间,再遇见这个问题,又卡住了,如此反反复复两三次,才发现自己对递归的理解依然很肤浅.今天无聊,重温<算法:c语言实现>一书,又遇见 ...

  7. 汉诺塔III 汉诺塔IV 汉诺塔V (规律)

    汉诺塔III Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Subm ...

  8. 运用Turtle实现汉诺塔的可视化运行(递归算法)

    运用Turtle实现汉诺塔的可视化运行(递归算法) 汉诺塔问题又名河内塔问题,是源于印度一个古老传说的益智玩具.大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆 ...

  9. HDUOJ---(1995)汉诺塔V

    汉诺塔V Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

随机推荐

  1. 无法向会话状态服务器发出会话状态请求。请确保 ASP.NET State Service (ASP.NET 状态服务)已启动,并且客户端端口与服务器端口相同。如果服务器位于远程计算机上,请检查。。。

    异常处理汇总-服 务 器 http://www.cnblogs.com/dunitian/p/4522983.html 无法向会话状态服务器发出会话状态请求.请确保 ASP.NET State Ser ...

  2. 前端学HTTP之日志记录

    前面的话 几乎所有的服务器和代理都会记录下它们所处理的HTTP事务摘要.这么做出于一系列的原因:跟踪使用情况.安全性.计费.错误检测等等.本文将谥介绍日志记录 记录内容 大多数情况下,日志的记录出于两 ...

  3. 前端学HTTP之Web主机托管

    前面的话 对内容资源的存储.协调以及管理的职责统称为Web主机托管.主机托管是Web服务器的主要功能之一.保存并提供内容,记录对内容的访问以及管理内容都离不开服务器.如果不想自行管理服务器所需的软硬件 ...

  4. jQuery学习之路(4)- 动画

    ▓▓▓▓▓▓ 大致介绍 通过jQuery中基本的动画方法,能够轻松地为网页添加非常精彩的视觉效果,给用户一种全新的体验 ▓▓▓▓▓▓ jQuery中的动画 ▓▓▓▓▓▓ show()和hide()方法 ...

  5. 免费开源的DotNet任务调度组件Quartz.NET(.NET组件介绍之五)

    很多的软件项目中都会使用到定时任务.定时轮询数据库同步,定时邮件通知等功能..NET Framework具有“内置”定时器功能,通过System.Timers.Timer类.在使用Timer类需要面对 ...

  6. C++ 拷贝构造函数和赋值运算符

    本文主要介绍了拷贝构造函数和赋值运算符的区别,以及在什么时候调用拷贝构造函数.什么情况下调用赋值运算符.最后,简单的分析了下深拷贝和浅拷贝的问题. 拷贝构造函数和赋值运算符 在默认情况下(用户没有定义 ...

  7. IdentityServer4 使用OpenID Connect添加用户身份验证

    使用IdentityServer4 实现OpenID Connect服务端,添加用户身份验证.客户端调用,实现授权. IdentityServer4 目前已更新至1.0 版,在之前的文章中有所介绍.I ...

  8. 微软开源代码编辑器monaco-editor

    官网上给出:”The Monaco Editor is the code editor that powers VS Code. A good page describing the code edi ...

  9. 5.0 JS中引用类型介绍

    其实,在前面的"js的六大数据类型"文章中稍微说了一下引用类型.前面我们说到js中有六大数据类型(五种基本数据类型 + 一种引用类型).下面的章节中,我们将详细讲解引用类型. 1. ...

  10. AutoIt实现Webdriver自动化测试文件上传

    在运用WebDriver进行自动化测试时,由于WebDriver自身的限制,对于上传文件时Windows弹出的文件选择窗口无法控制,通过在网上查找资料锁定使用AutoIt来控制文件上传窗口. Auto ...