算法笔记_013:汉诺塔问题(Java递归法和非递归法)
目录
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递归法和非递归法)的更多相关文章
- C语言之算法初步(汉诺塔--递归算法)
个人觉得汉诺塔这个递归算法比电子老鼠的难了一些,不过一旦理解了也还是可以的,其实网上也有很多代码,可以直接参考.记得大一开始时就做过汉诺塔的习题,但是那时代码写得很长很长,也是不理解递归的结果.现在想 ...
- JavaScript算法实现之汉诺塔(Hanoi)
目前前端新手,看到的不喜勿喷,还望大神指教. 随着Node.js,Angular.js,JQuery的流行,点燃了我学习JavaScript的热情!以后打算每天早上跟晚上抽2小时左右时间将经典的算法都 ...
- T2485 汉诺塔升级版(普及)(递归)
https://www.luogu.org/problem/show?pid=T2485 题目背景 汉诺塔升级了 题目描述 现在我们有N个圆盘和N个柱子,每个圆盘大小都不一样,大的圆盘不能放在小的圆盘 ...
- 汉诺塔问题java实现
问题描述 三个柱子,起初有若干个按大小关系顺序安放的盘子,需要全部移动到另外一个柱子上.移动规则:在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘. 解题思路 使用递归算法进行处理,实在理不 ...
- 汉诺塔问题其实很简单 Python 递归经典面试题
话不多说,上代码 1 def hanoi_move(n, source, dest, intermediate): 2 if n >= 1: # 递归出口,只剩一个盘子 3 hanoi_move ...
- 【C语言】汉诺塔问题
之前遇见这个问题,非常费劲地理解了,并写出代码,然后过段时间,再遇见这个问题,又卡住了,如此反反复复两三次,才发现自己对递归的理解依然很肤浅.今天无聊,重温<算法:c语言实现>一书,又遇见 ...
- 汉诺塔III 汉诺塔IV 汉诺塔V (规律)
汉诺塔III Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Subm ...
- 运用Turtle实现汉诺塔的可视化运行(递归算法)
运用Turtle实现汉诺塔的可视化运行(递归算法) 汉诺塔问题又名河内塔问题,是源于印度一个古老传说的益智玩具.大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆 ...
- HDUOJ---(1995)汉诺塔V
汉诺塔V Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submi ...
随机推荐
- 动画requestAnimationFrame
前言 在研究canvas的2D pixi.js库的时候,其动画的刷新都用requestAnimationFrame替代了setTimeout 或 setInterval 但是jQuery中还是采用了s ...
- .NET Core & ASP.NET Core 1.0在Redhat峰会上正式发布
众所周知,Red Hat和微软正在努力使.NET Core成为Red Hat企业版Linux (RHEL)系统上的一流开发平台选项.这个团队已经一起工作好几个月了,RHEL对.NET有许多需求.今天在 ...
- .NET 基础 一步步 一幕幕[面向对象之构造函数、析构函数]
构造函数.析构函数 构造函数: 语法: //无参的构造函数 [访问修饰符] 函数名() :函数名必须与类名相同. //有参的构造函数 [访问修饰符] 函数名(参数列表):函数名必须与类名相同. 作用: ...
- IE8/9 本地预览上传图片
本地预览的意思是,在选择图片之后先不上传到服务器,而是由一个<img>标签来预览本地的图片,非 IE8/9 浏览器可以从<input type="file"/&g ...
- SDWebImage源码解读 之 UIImage+GIF
第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...
- # PHP - 使用PHPMailer发邮件
PHPMailer支持多种邮件发送方式,使用起来非常简单 1.下载PHPMailer https://github.com/PHPMailer/PHPMailer,下载完成加压后, 把下边的两个文件复 ...
- Asp.net Core准备工作
1.安装环境 安装.Net Core SDK 安装VS2015 Update3 安装DotNetCore.1.0.1-VS2015Tools.Preview2.0.2.exe 2.新建Core工程 项 ...
- C++ 11 多线程--线程管理
说到多线程编程,那么就不得不提并行和并发,多线程是实现并发(并行)的一种手段.并行是指两个或多个独立的操作同时进行.注意这里是同时进行,区别于并发,在一个时间段内执行多个操作.在单核时代,多个线程是并 ...
- Node.js学习笔记——Node.js开发Web后台服务
一.简介 Node.js 是一个基于Google Chrome V8 引擎的 JavaScript 运行环境.Node.js 使用了一个事件驱动.非阻塞式 I/O 的模型,使其轻量又高效.Node.j ...
- JQuery的基础和应用
<参考文档> 1.什么是? DOM的作用:提供了一种动态的操作HTML元素的方法. jQuery是一个优秀的js库.用来操作HTML元素的工具. jQuery和DOM ...