河内塔算法: Python, C++ Code

⚡ 智能摘要

汉诺塔算法是一个经典的递归谜题,它将一叠圆盘在三个柱子之间移动,但永远不会将较大的圆盘放在较小的圆盘上面,清楚地说明了分而治之的原则。

  • 🗼 拼图设置: 源柱上堆叠着三个柱子和 n 个大小递减的圆盘,等待通过辅助柱子移动到目标柱子。
  • 📜 规则: 一次只能移动一个圆盘,任何柱子上只能移动最上面的圆盘,较大的圆盘不能放在较小的圆盘上。
  • 🔁 递归思想: 将 n-1 个圆盘移到辅助柱上,将最大的圆盘移到目标柱上,然后将 n-1 个圆盘从辅助柱移到目标柱上。
  • ⏱️ 时间复杂度: 解决 n 个圆盘需要 2^n – 1 次移动,因此时间复杂度为指数级的 O(2^n),随着 n 的增加而增长得非常快。
  • 🧠 空间复杂度: 递归栈一次最多可以容纳 n 帧,因此递归解决方案的空间复杂度为 O(n)。
  • 🛠️ 应用环境: 教授递归、备份轮换方案、基于堆栈的数据移动、机器人排序以及理解分治算法设计。

汉诺塔算法

汉诺塔是什么?

汉诺塔是一个数学谜题,由三根杆和一叠大小递减的圆盘组成。它也被称为梵天塔或卢卡斯塔,因为法国数学家爱德华·卢卡斯于1883年提出了这个谜题。这个谜题源于一个关于在三根杆之间移动金圆盘的传说。

这个拼图由三根杆和数量不定的堆叠圆盘组成。杆子排列成循环塔状,较大的圆盘堆叠在底部,较小的圆盘堆叠在顶部。

游戏开始时,我们有三个木桩或木杆。其中一个木桩(例如示例中的木桩 A)上堆叠着所有的圆盘。目标是在遵守一些特定规则的前提下,将整个圆盘堆从一根木杆(A)移动到另一根木杆(C)。

以下是谜题的初始设置:

汉诺塔问题

汉诺塔问题

这就是最终目标:

汉诺塔

汉诺塔规则

以下是汉诺塔的基本规则:

  • 在拼图的初始状态下,所有圆盘都堆叠在第一根杆上。
  • 最终状态下,杆一上的所有圆盘都堆叠在杆二或杆三上。
  • 任何时候都只能有一个圆盘从一根杆移动到另一根杆上。
  • 只有杆上的最上层圆盘才能移动。
  • 一张大圆盘不能放在一张小圆盘上面。

最初的传说是关于移动64个圆盘。祭司们按照规则一次只能移动一个圆盘。传说中预言,如果他们能够完成这项壮举,世界将会毁灭。在时间复杂度部分,我们将证明,对于n个圆盘的汉诺塔场景,需要2^n-1次移动。

因此,如果祭司们需要 1 秒钟才能移动一个圆盘,那么解决这个谜题的总时间将是 2^64 – 1 秒,或大约 584,942,417,356 年 26 天 7 小时 15 秒。

汉诺塔算法

解决汉诺塔问题的最常用方法是递归算法。首先,我们选择两根杆子作为起点和终点;备用杆子则作为辅助杆或帮手。

以下是解决汉诺塔难题的步骤:

  • 将最上面的 n-1 个盘子从源桩移到辅助桩。
  • 将第 n 个圆盘从源柱移动到目标柱。
  • 将剩余的 n-1 个圆盘从辅助柱移到目标柱。

注意: 如果我们只有一个磁盘,我们可以直接将其从源磁盘移动到目标磁盘。

如何解决汉诺塔难题

让我们用三个圆盘来说明这个算法。假设 A 为源点,B 为辅助点,C 为目标点。

步骤1) 最初,所有圆盘都堆放在 A 柱上。

解答汉诺塔谜题

现阶段:源点 = Peg A,目的地 = Peg C,助手 = Peg B。

现在,我们需要将最上面的 n-1 个盘子从源盘子移到辅助盘子。

注意: 虽然我们一次只能移动一个磁盘,但这一步将我们的 3 个磁盘问题简化为 2 个磁盘问题,这可以通过递归调用来处理。

步骤2) 当我们从桩 A 递归调用桩 B 为目标时,我们使用桩 C 作为辅助桩。

注意,我们又回到了汉诺塔问题的第一阶段,但这次是两个圆盘。我们将 n-1(即一个)圆盘从源点移动到辅助点,辅助点再将最小的圆盘从 A 柱移动到 C 柱。

解答汉诺塔谜题

现阶段:源点 = 桩 A,目的地 = 桩 B,助手 = 桩 C。

步骤3) 根据该算法,第 n 个(第 2 个)磁盘现在被转移到目的地,即桩 B。

解答汉诺塔谜题

现阶段:源点 = 桩 A,目的地 = 桩 B,助手 = 桩 C。

步骤4) 现在,我们将 n-1 号圆盘(圆盘一)从辅助柱 C 移动到目标柱 B,按照算法的第三阶段进行操作。

解答汉诺塔谜题

现阶段:源点 = 桩 A,目的地 = 桩 B,助手 = 桩 C。

步骤5) 递归调用完成后,我们回到算法第一阶段的先前设置。

步骤6) 在第二阶段,我们将磁盘 3 从源柱 A 移动到目标柱 C。

现阶段:源点 = 桩 A,目的地 = 桩 C,助手 = 桩 B。

步骤7) 下一个任务是将剩余的圆盘从辅助柱(柱子 B)移动到目标柱(柱子 C)。这次我们将使用原来的源柱(柱子 A)作为辅助柱。

解答汉诺塔谜题

步骤8) 由于我们不能同时移动两个磁盘,所以我们对磁盘 1 进行递归调用。根据我们的 算法此步骤的目标位置是桩 A。

解答汉诺塔谜题

现阶段:源点 = 桩 B,目的地 = 桩 A,助手 = 桩 C。

步骤9) 我们的递归调用已完成。现在我们将磁盘 2 从源磁盘移动到目标磁盘。

解答汉诺塔谜题

现阶段:源点 = 桩 B,目的地 = 桩 C,助手 = 桩 A。

步骤10) 最后,我们将剩余的 n-1 个磁盘(磁盘 1)从辅助磁盘移动到目标磁盘。

解答汉诺塔谜题

现阶段:源点 = 桩 A,目的地 = 桩 C,助手 = 桩 B。

昵称 Code 汉诺塔

START
Procedure Tower_Of_Hanoi(disk, source, dest, helper)
    IF disk == 1 THEN
        move disk from source to dest
    ELSE
        Tower_Of_Hanoi(disk - 1, source, helper, dest)
        move disk from source to dest
        Tower_Of_Hanoi(disk - 1, helper, dest, source)
    END IF
END Procedure

程序代码 C++

#include <bits/stdc++.h>
using namespace std;
void tower_of_hanoi(int num, string source, string dest, string helper) {
    if (num == 1) {
        cout << " Move disk 1 from tower " << source << " to tower " << dest << endl;
        return;
    }
    tower_of_hanoi(num - 1, source, helper, dest);
    cout << " Move disk " << num << " from tower " << source << " to tower " << dest << endl;
    tower_of_hanoi(num - 1, helper, dest, source);
}
int main() {
    int num;
    cin >> num;
    printf("The sequence of moves :\n");
    tower_of_hanoi(num, "I", "III", "II");
    return 0;
}

输出:

3
The sequence of moves :
Move disk 1 from tower I to tower III
Move disk 2 from tower I to tower II
Move disk 1 from tower III to tower II
Move disk 3 from tower I to tower III
Move disk 1 from tower II to tower I
Move disk 2 from tower II to tower III
Move disk 1 from tower I to tower III

程序代码 Python

def tower_of_hanoi(n, source, destination, helper):
    if n == 1:
        print("Move disk 1 from peg", source, "to peg", destination)
        return
    tower_of_hanoi(n - 1, source, helper, destination)
    print("Move disk", n, "from peg", source, "to peg", destination)
    tower_of_hanoi(n - 1, helper, destination, source)
# n = number of disks
n = 3
tower_of_hanoi(n, 'A', 'B', 'C')

输出:

Move disk 1 from peg A to peg B
Move disk 2 from peg A to peg C
Move disk 1 from peg B to peg C
Move disk 3 from peg A to peg B
Move disk 1 from peg C to peg A
Move disk 2 from peg C to peg B
Move disk 1 from peg A to peg B

汉诺塔的复杂性

以下是汉诺塔在时间和空间上的复杂性:

1)时间复杂度:

回顾算法,每次调用都会对 (n-1) 个磁盘进行两次递归调用。每次 (n-1) 次递归又会分解为 ((n-1)-1) 次递归,依此类推,直到达到单磁盘的基本情况。

适用于三张光盘:

  • 磁盘 3 对磁盘 2 调用递归函数两次。
  • 磁盘 2 对磁盘 1 调用递归函数两次。
  • 圆盘 1 以恒定时间移动,因此有时间求解三个圆盘。

以循环形式表达:

= 2 × (求解两个圆盘所需时间) + 移动圆盘 3 的常数时间

= 2 × (2 × 求解一个圆盘所需时间 + 移动圆盘 2 的常数时间) + 移动圆盘 3 的常数时间

= (2 × 2) × 移动圆盘 1 的常数时间 + 2 × 移动圆盘 2 的常数时间 + 移动圆盘 3 的常数时间

对于 n 个磁盘,则变为:

2正1 × 移动圆盘 1 + 2 的恒定时间正2 × 移动圆盘所需时间 2 + ….

这个等比数列的和为 O(2)n – 1),简化为 (2)n)时间复杂度呈指数级增长。

2)空间复杂度:

汉诺塔的空间复杂度为 O(n)。递归使用调用栈,而栈的最大深度等于 n,即圆盘的数量。这就是空间复杂度为 O(n) 的原因。

常见问题

汉诺塔算法是一个递归过程,它使用一个辅助柱将 n 个圆盘从源柱移动到目标柱,同时永远不会将较大的圆盘放在较小的圆盘上面。

n 个圆盘最少需要 2^n – 1 步。三个圆盘需要 7 步,四个圆盘需要 15 步,十个圆盘需要 1,023 步。

时间复杂度为 O(2^n),因为每增加一个圆盘,工作量就会翻倍。递归式 T(n) = 2T(n-1) + 1 的解为 2^n – 1,这是一个指数级复杂度。

空间复杂度为 O(n),因为递归调用栈为每个正在处理的磁盘保存一个帧。最大递归深度达到 n,因此所需的辅助内存与磁盘数量呈线性关系。

是的。迭代解决方案使用具有固定模式的循环:在奇数步时,循环地在钉子之间交换最小的圆盘;在偶数步时,进行唯一合法的非最小移动。

该算法教授递归,模拟存储的备份旋转方案,指导机械臂排序,并出现在衡量规划能力的神经心理学测试中。

强化学习智能体通过将每个圆盘的排列方式视为一种状态,将每次移动视为一个动作来解决汉诺塔问题。它是规划和分层策略学习的常用基准测试。

是的。GitHub Copilot、ChatGPT 和 Gemini 生成递归的汉诺塔解决方案 Python, C++和 Java开发者仍然应该验证基本情况和参数顺序。

总结一下这篇文章: