汉诺塔问题笔记(Java实现)

蹲街弑〆低调 提交于 2019-12-28 12:04:25

汉诺塔问题笔记(Java实现)

前言

这两天在B站跟着小甲鱼学习递归的时候遇到了汉诺塔问题,乍一听没想明白,动手画了画才明白过来,因为本人递归学的不太好,为了加深印象,也为了验证自己是否真的明白了,决定写一个笔记记录一下。

B站小甲鱼传送门:小甲鱼讲解数据结构和算法 p34 汉诺塔问题

问题描述

汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。(来自百度百科)

现在抛开问题的背景,汉诺塔问题可以简单描述如下:

  1. 初始状态:有三根柱子,其中第一根柱子上堆放着任意数量(n)的圆盘,另外两根是空柱子。
  2. 圆盘在任意柱子上的摆放遵循以下原则:在上方的圆盘半径不能超过其下方的圆盘半径。(这里为了简化问题,可以给所有的圆盘编号,最大的圆盘编号最大 n,最小的圆盘编号最小 1,那么也就是说在任意一根柱子上,圆盘的编号必须从下往上依次减小)
  3. 目标:将所有的圆盘从一开始的柱子移动到第三根柱子上,在移动过程中和结束状态下圆盘的摆放都必须遵循 2 中的原则。
  4. 要求:列出完成目标所需要的所有步骤。

hanoi-tower

解析

汉诺塔问题是递归中的经典问题,这里也只介绍一下递归的解法。(以下的 n 代表第 n 个圆盘,数字代表对应的圆盘,以简化描述)

我在遇到递归问题时,会去找到一个通用的描述,可以通过反复执行该描述来解决问题。汉诺塔问题的通用描述为:

为了将编号 1~n 的圆盘从第一根柱子移到第三根柱子上,需要执行以下三个步骤:

  1. 将除 n 外的所有圆盘从第一根柱子移动到第二根柱子上。
  2. 将 n 从第一根柱子移动到第三根柱子上。
  3. 将其他 n-1 个圆盘从第二根柱子移到第三根柱子上(也就是 n 上)。

因为无论 n 等于多少,这三个步骤都是必须执行且无法再进行简化的,接下来我们将三根柱子从一到三分别命名为,X、Y、Z。通用描述可以简化为:

为了将编号 1~n 的圆盘从 X 移到 Z 上,需要执行以下三个步骤:

  1. 将编号 1~n-1 的圆盘从 X 移动到 Y 上。
  2. 将 n 从 X 移动到 Z 上。
  3. 将编号 1~n-1 的圆盘从 Y 移到 Z 上(也就是 n 上)。

简化之后我们会很容易发现,三个步骤中的 1 和 3 其实都是另外三个步骤的开始,无非是将 n 换成了 n-1,三个柱子的顺序换了一下罢了。而步骤中的 2 是这个步骤中唯一不需要递归的步骤。因此每三个步骤中,就会有两个步骤开始新的属于他们的三个步骤。

那么在什么时候停止递归呢?是当三个步骤中的1 和 3 变为:将 1 ~2-1 的圆盘从 - 移到 - 上,此时因为这两个步骤可以直接实现,所以不需要递归,因而是递归的结束点。

画一个 “形象” 的图帮助理解一下:

(绿色方块代表可以直接被执行的步骤,灰色方块代表无法直接执行,需要执行箭头所指的步骤)在这里插入图片描述
在这里插入图片描述
当 n 继续变大,无非是层数不断增加,就不继续画出了。因为我是在画出了这个图之后恍然大悟的,希望对大家也有所帮助。

可以看到,在递归时,将圆盘从哪根柱子移到哪根柱子会发生变化,但是相应的步骤中的移动逻辑是不变的。接下来是 Java 代码的实现。

Java 代码实现

import java.util.Scanner;

class Hanoi {

	// n 表示将 1~n 号圆盘,x,y,z 分别代表第一、二、三根柱子
	public static void hanoi(int n, char x, char y, char z){
		if(n == 1){
			System.out.println(x + " --> " + z);
		}else{
			hanoi(n-1, x, z, y);
			System.out.println(x + " --> " + z);
			hanoi(n-1, y, x, z);
		}
	}

	public static void main(String[] args){
		System.out.print("Please input the number of layers of Hanoi Tower: ");
		try{
			Scanner input = new Scanner(System.in);
			int n = input.nextInt();
			System.out.println("The following are the movements: ");
			hanoi(n, 'X', 'Y', 'Z');
		}catch (Exception e) {
			System.out.println(e);
		}	
	}
}

第一次写笔记,有错误的地方欢迎指正,最后祝大家圣诞节快乐~

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