汉诺塔问题

汉诺塔(又称河内塔)问题其实是印度的一个古老的传说。开天辟地的神勃拉玛(和中国的盘古差不多的神吧)在一个庙里留下了三根金刚石的棒,第一根上面套着64个圆的金片,最大的一个在底下,其余一个比一 个小,依次叠上去,庙里的众僧不倦地把它们一个个地从这根棒搬到另一根棒上,规定可利用中间的一根棒作为帮助,但每次只能搬一个,而且大的不能放在小的上 面。计算结果非常恐怖(移动圆片的次数)18446744073709551615,众僧们即便是耗尽毕生精力也不可能完成金片的移动了。要求:输入一个正整数n,表示有n个盘片在第一根柱子上。输出操作序列, 格式为“移动 t从 x 到 y”。每个操作一行,表示把x柱子上的编号为t的盘片挪到柱子y上。柱子编号为A,B,C,你要用最少的操作把所有的盘子从A柱子上转移到C柱子上。


图例:

 

分析:

如果是初次接触类似的问题,乍看之下肯定会感觉无从下手。

要把64个圆盘从a柱子移动到c柱子上,第一步应该怎么做?虽然可以肯定,第一步唯一的选择是移动a最上面的那个圆盘,但是应该将其移到b还是c呢?很难确定。因为接下来的第二步、第三步……直到最后一步,看起来都是很难确定的。能立即确定的是最后一步:最后一步的盘子肯定也是a最上面那个圆盘,并且是由a或b移动到c——此前已经将63个圆盘移动到了c上。

也许你会说,管他呢,先随便试着移动一下好了。如果你这么做,你会发现,接下来你会面临越来越多类似的选择,对每一个选择都“试”一下的话,你会偏离正确的道路越来越远,直到你发现你接下来无法进行为止。

如果将这个问题的盘子数量减为10个或更少,就不会有太大的问题了。但盘子数量为64的话,你一共需要移动约1800亿亿步(18,446,744,073,709,551,615),才能最终完成整个过程。这是一个天文数字,没有人能够在有生之年通过手动的方式来完成它。即使借助于计算机,假设计算机每秒能够移动100万步,那么约需要18万亿秒,即58万年。将计算机的速度再提高1000倍,即每秒10亿步,也需要584年才能够完成。注:在我的笔记本电脑上,每秒大约能够移动6~8百万步。

虽然64个盘子超出了人力和现代计算机的能力,但至少对于计算机来说,这不是一个无法完成的任务,因为与我们人类不同,计算机的能力在不断提高。

 

分解问题

 

一股脑地考虑每一步如何移动很困难,我们可以换个思路。先假设除最下面的盘子之外,我们已经成功地将上面的63个盘子移到了b柱,此时只要将最下面的盘子由a移动到c即可。如图:

当最大的盘子由a移到c后,b上是余下的63个盘子,a为空。因此现在的目标就变成了将这63个盘子由b移到c。这个问题和原来的问题完全一样,只是由a柱换为了b柱,规模由64变为了63。因此可以采用相同的方法,先将上面的62个盘子由b移到a,再将最下面的盘子移到c……对照下面的过程,试着是否能找到规律:

  1. 将b柱子作为辅助,把a上的63个圆盘移动到b上
  2. 将a上最后一个圆盘移动到c
  3. 将a作为辅助,把b上的62个圆盘移动到a上
  4. 将b上的最后一个圆盘移动到c
  5. ......

也许你已经发现规律了,即每次都是先将其他圆盘移动到辅助柱子上,并将最底下的圆盘移到c柱子上,然后再把原先的柱子作为辅助柱子,并重复此过程。

这个过程称为递归,即定义一组基本操作,这组操作将规模小一点(或大一点)的操作当做一个整体——无需关心它的细节,只当它已经完成了——然后执行剩下的操作。而在更小或更大的规模中也依此操作,直到规模达到预定值。

在数学上,有些公式就是采用递归的方式定义的。例如阶乘和斐波那契数列(Fibonacci Sequence)。前者的公式为:

规定0!=1!=1,对于n>=2,有n!=n*(n-1)!

这里的n-1就是比n规模略小的阶乘,而1就是规模的最小值(预定值)(0是作为特殊值而专门规定的)。

著名的斐波那契数列定义如下,可以看出,f(n)是由规模更小一些的f(n-1)和f(n-2)推导出来的:

f(0)=0,f(1)=1
f(n)=f(n-1)+f(n-2) (n>=2)

因此,递归实际上就是用自己来定义自己。

回到前面汉诺塔的问题上来。我们假设函数func(n, a, b, c)用于将n个圆盘由a移动到c,b作为辅助柱子。那么我们可以这样实现这个递归过程:

func:
if n!=0 then ;预定值 func(n-1, a, c, b) ;将n-1个盘子由a移动到b,以c为辅助柱子(注意参数顺序) move a[n] to c ;将a上的最后一个盘子移动到c func(n-1, b, a, c) ;将n-1个盘子由b移动到c,以a为辅助柱子 endif ;完成


func中有两个递归调用,它们的规模刚好比n小1。注释说明了每行代码的作用和意图。正如注释里所强调的那样,注意参数的顺序——参数位置不同,其代表的意义也不一样。

第一个递归调用以c作为辅助柱子,这没有问题,因为c柱子的最下面的k个圆盘一定是所有圆盘中最大的k个,因此将其作为辅助柱子不会出现大圆盘在小圆盘之上的情况。






代码:

	public static void main(String[] args){
		Scanner sc=new Scanner(System.in);
		int i=sc.nextInt();
		char a ='A',b='B',c='C';
		hanio(i,a,b,c);
	}
	public static void hanio(int n,char a,char b,char c){
		if(n==1)
			System.out.println("移动"+n+"号盘子从"+a+"到"+c);
		else{
			hanio(n-1,a,c,b);//把上面n-1个盘子从a借助b搬到c
			System.out.println("移动"+n+"号盘子从"+a+"到"+c);//紧接着直接把n搬动c
			hanio(n-1,b,a,c);//再把b上的n-1个盘子借助a搬到c
		}
	}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值