问题:
汉诺塔问题(Hanoi),又称河内塔问题,是一个经典的数学游戏和益智玩具。它起源于印度,传说中一个古老的庙里,有一个柱子,在柱子上,有三个针,最左边的针上套着 64 个圆盘,大小不等,大的在下,小的在上。有一个和尚想把这 64 个圆盘从最左边的针上移到最右边的针上,但必须遵循以下规则:
每次只能移动一个圆盘;
每个圆盘只能放到比它大的圆盘上面。
问该如何移动圆盘,才能既按规定完成移动,且在移动圆盘的过程中始终保持稳定状态?
递归解法思路
我们定义一个 move
函数来实现盘子移动的逻辑。函数有四个参数:n
表示移动的盘子数量,a
、b
和 c
分别表示三根柱子(起始柱、辅助柱和目标柱)。
- 当
n
等于 1 时,表示只剩下一个盘子需要移动,那么直接将该盘子从起始柱a
移动到目标柱c
上,并输出移动的步骤和总步数。- 否则,我们将问题拆分为三个步骤:
- 将
n-1
个盘子从起始柱a
通过辅助柱c
移动到目标柱b
上。- 将剩下的最后一个盘子从起始柱
a
直接移动到目标柱c
上。- 将之前移动到辅助柱
b
上的n-1
个盘子通过起始柱a
移动到目标柱c
上。
这样,我们通过递归调用 move
函数实现了将 n 个盘子从柱子 A 移动到柱子 C 上的过程。
move函数代码:
// 定义递归移动函数 void move(int n, int a, int b, int c) { if (n == 1) { cout << a << "->" << c << endl; // 输出移动路径 sum++; // 步数+1 } else { move(n - 1, a, c, b); // 将 n-1 个圆盘从柱子a经过柱子c移动到柱子b上(递归调用) move(1, a, b, c); // 将最下面的一个圆盘从柱子a直接移动到柱子c上(递归调用) move(n - 1, b, a, c); // 将 n-1 个圆盘从柱子b经过柱子a移动到柱子c上(递归调用) } }
主程序:
#include<iostream> using namespace std; void move(int n, int a, int b, int c);// 定义移动函数, //参数包括圆盘数量n以及三个柱子的编号a、b、c int sum = 0; // 记录移动步数 int main() { while (1) { int n; cout << "请输入要移动的个数: "; cin >> n; move(n, 1, 2, 3); // 调用移动函数 cout << "总步骤:" << sum << "步" << endl; sum = 0; // 重置步数为0 } return 0; }
尾递归优化(参考其它博客):
修改后的move函数:
// 尾递归移动函数的实现 void move(int n, int a, int b, int c, int &sum) { if (n == 1) { cout << a << "->" << c << endl; // 将编号为a的柱子上的圆盘移动到编号为c的柱子上(输出移动路径) sum++; // 步数+1 return; // 返回,结束递归调用 } move(n - 1, a, c, b, sum); // 将 n-1 个圆盘从柱子a经过柱子c移动到柱子b上(尾递归调用) cout << a << "->" << c << endl; // 将编号为a的柱子上的圆盘移动到编号为c的柱子上 //(直接输出移动路径) sum++; // 步数+1 move(n - 1, b, a, c, sum); // 将 n-1 个圆盘从柱子b经过柱子a移动到柱子c上(尾递归调用) }
注:其中move(1,a,b,c)由 cout << a << "->" << c << endl;替换
尾递归优化的原理:
是通过将递归调用放置在函数的最后一个操作,从而达到减少函数调用开销和避免栈溢出的目的。
在普通的递归中,每次递归调用都需要在内存中创建一个新的函数调用栈帧,用于保存函数的局部变量、参数以及返回地址等信息。递归的深度增加时,这些栈帧会不断累积,导致内存消耗过大,甚至发生栈溢出。
而尾递归优化则在递归的最后一步操作完成后,直接返回结果,而不再进行额外的递归调用。这样,当前函数调用的栈帧就可以被重用,不再需要保留,从而减少了内存的使用。同时,由于没有新的函数调用,也就不会进一步增加递归的深度,避免了栈溢出的风险。
在汉诺塔问题中,尾递归优化的实现思路是将移动操作放置在进行递归调用之后,确保在递归过程中每一步的移动路径都会被立即输出,并累加步数。这样,在递归回溯的过程中,不会出现过多的函数调用栈帧,从而达到优化的效果。
总结起来,尾递归优化的原理是通过将递归调用放置在函数的最后一个操作,减少了函数调用开销和递归深度,提高了程序的效率和性能。
时间复杂度:
假设总的移动步数为 sum,每次移动时 sum 自增 1。经过分析可得总的移动步数为 sum = 2 * sum(n-1) + 1。 递归深度为 n,总步数和n的关系为 T(n)=2^n-1,所以时间复杂度为O(2^n)。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/86222.html