对汉诺塔递归算法的一种理解方式

对汉诺塔递归算法的一种理解方式

我们先来看一下什么是汉诺塔问题:

法国数学家爱德华·卢卡斯曾编写过一个印度的古老传说:
在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。
-此处摘自百度

哈诺塔简图
http://www.4399.com/flash/109504_1.htm
开始之前可以试着玩玩这个小游戏可以加深对这个问题的理解。

汉诺塔问题-思考
要想解决这个问题,我们先来找规律->牵一发而动全身

抽象成简单三步走
第一步:将n-1层移动到B位
第二步:将第n层移动到C位
第三步:将刚才移到B位的n-1层移到C位
//???思考??? 
1.做第一步过程中要做什么事?
答:“将n-1层移动到B位”的过程中,要做这些事:
第一步:将n-2层移动到C位 
第二步:将第n-1层移动到B位
第三步:将刚才移动到C位的n-2层移动到B位
***这个过程要做的事情是不是和原来最初始的三步是类似的事?*** 
2.那么最开始做第三步过程中要做什么事?
答:“将刚才移到B位的n-1层移到C位”的过程中,要做这些事:
第一步:将n-2层移动到A位
第二步:将第n-1层移动到C位
第三步:将刚才移动到A位的n-2层移动到C位
***这个过程要做的事情是不是和原来最原始的三部也是类似的事?***
说明这个问题求解的过程我们可以用一个统一的函数,通过自己调用自己来实现过程的复现性;
//再思考是不是这样的:
假如,我们把这个问题的求解过程设定为一个函数hanuo:
那么这个函数包含了第一步,第二步和第三步要做的事
而第一步执行的过程中要执行hanuo过程, 
第二步,做的事情是打印出移动一层的做法 
第三步,要做的事情也是要执行hanuo过程  
那什么时候结束一个过程?(函数调用结束后返回的门) 
问得好!想一想就可以知道了,完成这一整件事的最后一步是不是就是希望A位的第一层移动到C位
理清楚了吗?
我们来抽象出hanuo过程,也就是回到这个问题的本质,到底要做什么事?
那不是很简单吗?
!!!就是将n层塔从原始位置先将n-1层塔移动到中转位置,然后将第n层移动到目标位置后将刚才中转位置的塔移动到目标位置!!! 
假如这个hanuo函数的定义是这样的
hanuo(打算转移的塔层数int n,原始位置char P1,中转位置char P2,目标位置char P3)
我们试着写出这个过程 
hanuo(int n,char P1,char P2,char P3)
{
	//第一步:将n-1层移动到中转位置->移动过程又变成原始中转目标位置问题 
	hanuo(n-1,P1,P3,P2); 
	//第二步:将第n层移动到目标位置(因为原始位置上剩下第n层) 
	printf("%c移动到%c",P1,P3);
	// 第三步:将刚才移到中转位置的n-1层移到目标位置->移动过程又变成原始中转目标位置问题 
	hanuo(n-1,P2,P1,P3);
}
//单纯这么写可不可以?
不可以,这个函数没有出口,永远调用不完,像头吃尾的贪吃蛇死循环,一条路走到黑,当内存爆满时,非自然退出,求解失败
所以要有一个函数的出口,刚才也说了,函数完完整整的结束的最后一步,也就是原始位置的第1层要跑到目标位置
所以,加上条件判断
hanuo(int n,char P1,char P2,char P3)
{
	if(n==1)
	{
		printf("%c移动到%c\n",P1,P3); //当原始位置只有一块板时,直接将它移到目标位置就行啦! 
	} 
	else
	{
	//第一步:将n-1层移动到中转位置->移动过程又变成原始中转目标位置问题 
	hanuo(n-1,P1,P3,P2); 
	//第二步:将第n层移动到目标位置(因为原始位置上剩下第n层) 
	printf("%c移动到%c\n",P1,P3);
	// 第三步:将刚才移到中转位置的n-1层移到目标位置->移动过程又变成原始中转目标位置问题 
	hanuo(n-1,P2,P1,P3);
	}
}
整体思路: 
欲将n层塔移动到C位,
先让n-1层移动到B位,然后再将第n层移动到C位后视而不见(游戏规则可知)
将n-2层移动到A位,让第n-1层移动到C位,然后再将第n-1层视而不见
将n-3层移动到B位,让第n-2层移动到C位,然后再将第n-2层视而不见
将n-4层移动到A位,让第n-3层移动到C位,然后再将第n-3层视而不见
将n-5层移动到B位,让第n-4层移动到C位,然后再将第n-4层视而不见
......
欲将1层移动到某位(根据塔的实际层数而不同),先让第2层移动到C位,然后再将第2层视而不见
此时只剩下1层,将第1层移动到C位,结束
其实只要明白到底计算机在做什么事就行,不必要深入去追究每一步到底怎么做的,因为递归的交叉过程非常复杂,而且繁琐,
就像循环一样,我们只要知道这样做的结果就是答案,而不必要去关心具体的每一步是是怎么做的,因为那样递归就失去了意义
应该要从宏观的角度去理解这种思想,这才是学习的重点哦^-^! 
p.s递归可以理解为要完成一件事之前必须先完成另外一件事 

附上参考代码:

#include<stdio.h>
void hanuo(int n,char P1,char P2,char P3);
int main()
{
	int n;
	printf("请输入塔的总层数:");
	scanf("%d",&n);
	hanuo(n,'A','B','C');
	return 0; 
} 

void hanuo(int n,char P1,char P2,char P3)
{
	if(n==1)
	{
		printf("%c移动到%c\n",P1,P3); //当原始位置只有一块板时,直接将它移到目标位置就行啦! 
	} 
	else
	{
	//第一步:将n-1层移动到中转位置->移动过程又变成原始中转目标位置问题 
	hanuo(n-1,P1,P3,P2); 
	//第二步:将第n层移动到目标位置(因为原始位置上剩下第n层) 
	printf("%c移动到%c\n",P1,P3);
	// 第三步:将刚才移到中转位置的n-1层移到目标位置->移动过程又变成原始中转目标位置问题 
	hanuo(n-1,P2,P1,P3);
	}
}
//http://www.4399.com/flash/109504_1.htm试着玩玩这个小游戏可以加深理解 

-pakqoo 笔记于2019年02月27日

文末碎碎念:
不管这个传说的可信度有多大,如果考虑一下把64片金片,由一根针上移到另一根针上,并且始终保持上小下大的顺序。这需要多少次移动呢?这里需要递归的方法。假设有n片,移动次数是f(n).显然f(1)=1,f(2)=3,f(3)=7,且f(k+1)=2*f(k)+1。此后不难证明f(n)=2^n-1。n=64时,
假如每秒钟一次,共需多长时间呢?一个平年365天有31536000 秒,闰年366天有31622400秒,平均每年31556952秒,计算一下:
18446744073709551615秒
这表明移完这些金片需要5845.54亿年以上,而地球存在至今不过45亿年,太阳系的预期寿命据说也就是数百亿年。真的过了5845.54亿年,不说太阳系和银河系,至少地球上的一切生命,连同梵塔、庙宇等,都早已经灰飞烟灭。(-此处摘自百度)

–分割线–
2019年08月31日 新增
之前第一次写这篇文章的时候花了较大的篇幅解释求解过程,用的是C语言。
今天写代码的时候又碰到这个汉诺塔问题,又Java语言重新写了一遍,又有了新的感悟。
代码还是一样的,就看看注释吧!

/*
 * 汉诺塔问题求解
 * 问题:将A柱子上的n个圆盘(从上到下编号为1 2 3 ... n,盘子依次增大),通过B柱子转移到C柱子。
 * 要求:每次移动一个盘子,移动的过程中必须保证上面的盘子比下面的盘子小,不能出现上面的盘子比下面的盘子大的情况。
 * 打印出每一步的移动方法,直至完成任务。
 * 递归解决问题。
 * */

import java.util.Scanner;

public class Main {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		hanuo(n, 'A', 'B', 'C');
	}
	//打印移动操作
	public static void move(char x,char y) {
		//移动操作即将某个盘子从主柱直接移动到目标柱的操作
		System.out.println(x+"->"+y);
	}
	//汉诺塔求解操作
	public static void hanuo(int n,char x, char y ,char z) {
		//函数本身的含义是完整的:将x主柱子上的n个盘子通过中转柱y移到目标柱z
		//函数出口,当主柱上的盘子数为1时,把这块盘子直接移到目标柱上即可
		if(n == 1) {
			move(x,z);
			return;
		}
		//第一步,将主柱上的n-1个盘子转移到中间柱
		hanuo(n-1, x, z, y);
		//第二步,将主柱的剩下的那一个盘子直接移动到目标柱
		move(x, z);
		//第三步,将中间柱的n-1个盘子移动到目标柱
		hanuo(n-1, y, x, z);
	}
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值