算法分析课之汉诺塔问题(Hanoi)递归思想

 问题:

汉诺塔问题(Hanoi),又称河内塔问题,是一个经典的数学游戏和益智玩具。它起源于印度,传说中一个古老的庙里,有一个柱子,在柱子上,有三个针,最左边的针上套着 64 个圆盘,大小不等,大的在下,小的在上。有一个和尚想把这 64 个圆盘从最左边的针上移到最右边的针上,但必须遵循以下规则:

每次只能移动一个圆盘;
每个圆盘只能放到比它大的圆盘上面。
问该如何移动圆盘,才能既按规定完成移动,且在移动圆盘的过程中始终保持稳定状态?

递归解法思路

我们定义一个 move 函数来实现盘子移动的逻辑。函数有四个参数:n 表示移动的盘子数量,abc 分别表示三根柱子(起始柱、辅助柱和目标柱)。

  1. n 等于 1 时,表示只剩下一个盘子需要移动,那么直接将该盘子从起始柱 a 移动到目标柱 c 上,并输出移动的步骤和总步数。

  2. 否则,我们将问题拆分为三个步骤:

    • 将 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)

部分数目的递归过程:

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辣鲨椒鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值