汉诺塔相信很多人小时候都玩过这样的游戏,这是源于印度的古老传说,大家可千万不要小看这个游戏,里面体现了古人的大智慧,在这里我们能学到最直观的演示方法,本篇主要是针对汉诺塔的问题进行分析和代码展示。
一、前言
汉诺塔,又称河内塔,是一个益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

二、解题思路
思路很简单:有三根相邻的柱子,从左到右分别为:托盘A、托盘B、托盘C;
托盘A按照“从下到上、从大到小”叠放着3个不同大小的盘子;

移动步骤
第一步:将盘子1从托盘A移动至托盘C

第二步:将盘子2从托盘A移动至托盘B

第三步:将盘子1从托盘C移动至托盘B

第四步:将盘子3从托盘A移动至托盘C

第五步:将盘子1从托盘B移动至托盘A

第六步:将盘子2从托盘B移动至托盘C

第七步:将盘子1从托盘A移动至托盘C

动图演示

是不是很简单?
我们从上面移动的过程中,可以看到,托盘B始终作为一个过渡的存在,并且可以把它想象成中转柱;
那么我们就可以这样理解:
托盘A:起始柱A;
托盘B:中转柱B;
托盘C:目标柱C
三、代码实现
递归思想
在解决汉诺塔问题之前,我们先来了解一下递归的思想。递归是一种强大的编程技巧,它允许函数调用自身,就像一个神奇的魔法,能把一个复杂的大问题层层转化为与原问题相似但规模更小的问题,直至问题小到可以直接解决 。就好比剥洋葱,我们每次都去掉一层,不断重复这个过程,直到只剩下最核心的部分。例如,计算阶乘是一个典型的递归应用。阶乘的定义为:n! = n * (n - 1) * (n - 2) *... * 1 。用递归的方式来实现,就是先定义 0! 和 1! 为 1(这是递归的终止条件),然后对于 n > 1 的情况,n! 可以通过调用 (n - 1)! 来计算,即 n! = n * (n - 1)! 。
不过,递归并非无拘无束,它需要满足两个关键条件:
- 终止条件:必须有一个明确的终止条件,当满足这个条件时,递归调用就会停止,避免陷入无限循环。例如在计算阶乘时,当 n 为 0 或 1 时,就直接返回 1,不再继续递归。
- 递归关系:能够将大问题分解为小问题,并且小问题的解决方式与大问题相同,只是规模更小。比如在计算 n! 时,通过将其转化为计算 (n - 1)! ,问题的本质没有改变,只是数据规模从 n 减小到了 n - 1 。
C语言代码
#include <stdio.h>
void move(int n, char pos1, char pos3)
{
//打印移动的过程
// 1代表上面最小的盘子
// 2代表中间位置的盘子
// 3代表下面最大的盘子
printf("盘子%d: 从 %c柱 移动到 %c柱\n", n, pos1, pos3);
}
void Hanoi(int n, char pos1, char pos2, char pos3)
{
//如果是1个盘子,直接从起始柱A移动到目标柱C
if (n == 1)
{
move(n, pos1, pos3);
}
else
{
//如果盘子大于1个,需要把n-1个盘子,从起始柱pos1,通过目标柱pos3,移动到中转柱pos2
Hanoi(n-1, pos1, pos3, pos2);
//此时pos1上的n-1个盘子全部移动pos2上去了,那么可以直接把pos1上剩下的1个盘子,直接移动到pos3上
move(n, pos1, pos3);
//把pos2剩下的n-1个盘子,通过中转位置pos1,移动到目标位置pos3
Hanoi(n-1, pos2, pos1, pos3);
}
}
int main()
{
//盘子个数
int n = 3;
//起始柱A
char pos1 = 'A';
//中转柱B
char pos2 = 'B';
//目标柱C
char pos3 = 'C';
printf("移动%d个盘子的步骤如下↓\n", n);
//汉诺塔函数
Hanoi(n, pos1, pos2, pos3);
return 0;
}
移动三个盘子步骤
移动3个盘子的步骤如下↓
盘子1: 从 A柱 移动到 C柱
盘子2: 从 A柱 移动到 B柱
盘子1: 从 C柱 移动到 B柱
盘子3: 从 A柱 移动到 C柱
盘子1: 从 B柱 移动到 A柱
盘子2: 从 B柱 移动到 C柱
盘子1: 从 A柱 移动到 C柱
移动四个盘子步骤
盘子1: 从 A柱 移动到 B柱
盘子2: 从 A柱 移动到 C柱
盘子1: 从 B柱 移动到 C柱
盘子3: 从 A柱 移动到 B柱
盘子1: 从 C柱 移动到 A柱
盘子2: 从 C柱 移动到 B柱
盘子1: 从 A柱 移动到 B柱
盘子4: 从 A柱 移动到 C柱
盘子1: 从 B柱 移动到 C柱
盘子2: 从 B柱 移动到 A柱
盘子1: 从 C柱 移动到 A柱
盘子3: 从 B柱 移动到 C柱
盘子1: 从 A柱 移动到 B柱
盘子2: 从 A柱 移动到 C柱
盘子1: 从 B柱 移动到 C柱
四、算法效率的考量
(一)时间复杂度
汉诺塔问题递归算法的时间复杂度为\(O(2^n)\)。这是因为在递归过程中,每次递归调用都会产生两个子问题,即移动\(n-1\)个圆盘的操作会被执行两次。随着递归层数的增加,子问题的数量呈指数级增长。具体来说,对于\(n\)个圆盘,需要执行\(2^n - 1\)次移动操作。当\(n\)较小时,\(2^n\)的增长速度可能不太明显,但当\(n\)逐渐增大时,时间复杂度的增长将变得极为迅速,导致计算量呈指数级上升。例如,当\(n = 10\)时,移动次数为\(2^{10}-1 = 1023\)次;当\(n = 20\)时,移动次数则高达\(2^{20}-1 = 1048575\)次 ,这使得解决大规模汉诺塔问题的时间成本变得非常高昂。
(二)空间复杂度
空间复杂度主要取决于递归调用的深度。在汉诺塔问题中,递归调用的深度与圆盘的数量\(n\)相等。因为在每一层递归中,系统需要为函数调用分配栈空间,用于保存函数的参数、局部变量和返回地址等信息。随着递归的进行,栈空间不断被占用,直到达到最大递归深度。当递归返回时,栈空间逐渐被释放。由于递归调用的最大深度为\(n\),所以汉诺塔问题的空间复杂度为\(O(n)\)。尽管空间复杂度的增长相对较为线性,但在处理非常大的\(n\)值时,仍然可能会面临栈溢出的风险,这也是在实际应用中需要考虑的问题之一。
9万+



被折叠的 条评论
为什么被折叠?



