1209: 【例】【基础】经典递归问题——汉诺塔
题目描述
汉诺塔(又称河内塔)问题是印度的一个古老的传说。开天辟地的神勃拉玛在一个庙里留下了三根金刚石的棒,第一根上面套着64个圆的金片,最大的一个在底下,其余一个比一个小,依次叠上去,庙里的众僧不倦地把它们一个个地从这根棒搬到另一根棒上,规定可利用中间的一根棒作为帮助,但每次只能搬一个,而且大的不能放在小的上面。
面对庞大的数字(移动圆片的次数)18446744073709551615,看来,众僧们耗尽毕生精力也不可能完成金片的移动。
后来,这个传说就演变为汉诺塔游戏:
1.有三根杆子A,B,C。A杆上有若干碟子
2.每次移动一块碟子,小的只能叠在大的上面
3.把所有碟子从A杆全部移到C杆上
经过研究发现,汉诺塔的破解很简单,就是按照移动规则向一个方向移动金片:
如3阶汉诺塔的移动:A→C,A→B,C→B,A→C,B→A,B→C,A→C
此外,汉诺塔问题也是程序设计中的经典递归问题。
算法思路:
1.如果只有一个金片,则把该金片从源移动到目标棒,结束。
2.如果有n个金片,则把前n-1个金片移动到辅助的棒,然后把自己移动到目标棒,最后再把前n-1个移动到目标棒.
输入
输出
样例输入 复制
3
样例输出 复制
A To C
A To B
C To B
A To C
B To A
B To C
A To C
提示
二.抽象为数学问题:
如下图所示,从左到右有A、B、C三根柱子,其中A柱子上面有从小叠到大的n个圆盘,现要求将A柱子上的圆盘移到C柱子上去,期间只有一个原则:一次只能移到一个盘子且大盘子不能在小盘子上面,求移动的步骤和移动的次数
当 n = 1 时(只有一个盘子)
-
• 第1步:直接将1号盘子从A柱移动到C柱。总移动次数 = 1 次
当 n = 2 时(有两个盘子)
-
• 第1步:将1号盘子(较小的)从A柱移动到B柱。
-
• 第2步:将2号盘子(较大的)从A柱移动到C柱。
-
• 第3步:将1号盘子从B柱移动到C柱上2号盘子之上。总移动次数 = 3 次
当 n = 3 时(有三个盘子)
-
• 第1步:将1号盘子从A柱移动到C柱。
-
• 第2步:将2号盘子从A柱移动到B柱。
-
• 第3步:将1号盘子从C柱移动到B柱,放在2号盘子上。
-
• 第4步:将3号盘子(最大的)从A柱移动到C柱。
-
• 第5步:将1号盘子从B柱移动到A柱。
-
• 第6步:将2号盘子从B柱移动到C柱,放在3号盘子上。
-
• 第7步:将1号盘子从A柱移动到C柱,放在所有盘子上。总移动次数 = 7 次
观察规律
通过观察上述几个例子,我们可以发现,每当盘子数量增加1时,移动次数就会翻倍并加1。这是因为要移动n个盘子到目标柱子,我们首先需要移动上面的n-1个盘子到辅助柱子,然后移动最大的盘子到目标柱子,最后再将那n-1个盘子从辅助柱子移动到目标柱子上。因此,对于n个盘子的情况,总移动次数符合以下规律:
-
• 对于1个盘子: 次
-
• 对于2个盘子: 次
-
• 对于3个盘子: 次
-
• 以此类推,对于n个盘子,总移动次数为:2n - 1次
调用方法的栈机制:(特点:先进后出)
从主线程开始调用方法(函数)进行不停的压栈和出栈操作,函数的调用就是将函数压如栈中,函数的结束就是函数出栈的过程,这样就保证了方法调用的顺序流,即当函数出现多层嵌套时,需要从外到内一层层把函数压入栈中,最后栈顶的函数先执行结束(最内层的函数先执行结束)后出栈,再倒数第二层的函数执行结束出栈,到最后,第一个进栈的函数调用结束后从栈中弹出回到主线程,并且结束。
算法分析(递归算法):
我们在利用计算机求汉诺塔问题时,必不可少的一步是对整个实现求解进行算法分析。到目前为止,求解汉诺塔问题最简单的算法还是同过递归来求,至于是什么是递归,递归实现的机制是什么,我们说的简单点就是自己是一个方法或者说是函数,但是在自己这个函数里有调用自己这个函数的语句,而这个调用怎么才能调用结束呢?,这里还必须有一个结束点,或者具体的说是在调用到某一次后函数能返回一个确定的值,接着倒数第二个就能返回一个确定的值,一直到第一次调用的这个函数能返回一个确定的值。
实现这个算法可以简单分为三个步骤:
(1) 把n-1个盘子由A 移到 B;
(2) 把第n个盘子由 A移到 C;
(3) 把n-1个盘子由B 移到 C;
我们不难发现,移到的步数必定为奇数步:
(1)中间的一步是把最大的一个盘子由A移到C上去;
(2)中间一步之上可以看成把A上n-1个盘子通过借助辅助塔(C塔)移到了B上,
(3)中间一步之下可以看成把B上n-1个盘子通过借助辅助塔(A塔)移到了C上;
#include <iostream> using namespace std; // 移动汉诺塔盘子的函数,n是盘子数量,src是起始柱子,aux是辅助柱子,dest是目标柱子 void hanoi(int n, char src, char aux, char dest) { if (n == 1) { // 递归终止条件 cout << src << " To " << dest << endl; return; } // 移动上面 n-1 个盘子到辅助柱子 hanoi(n-1, src, dest, aux); // 移动最底下的盘子到目标柱子 cout << src << " To " << dest << endl; // 将辅助柱子上的盘子移动到目标柱子 hanoi(n-1, aux, src, dest); } int main() { int n; // 盘子数量 cin >> n; // 调用汉诺塔函数,假设初始时所有盘子都在 'A' 柱子上,目标是移动到 'C' 柱子,'B' 作为辅助柱子 hanoi(n, 'A', 'B', 'C'); return 0; }