
(递归的应用)
大家好,很高兴又和大家见面啦!!! 在上一篇内容中,我们系统学习了递归这一重要算法思想的核心要点:
理论的价值在于指导实践。今天,我们将通过经典的汉诺塔问题,将递归知识付诸实战应用,检验学习成果并提升算法设计能力。 无论你是希望巩固递归这一关键技术点,还是准备应对算法面试挑战,本次实战都将为你提供宝贵的学习经验。 让我们一同开启这段递归思维的深度训练,体验算法设计的精妙之处!
相关标签:递归、数组 题目难度:简单 题目描述: 在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。 一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。 移动圆盘时受到以下限制:
请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。 你需要原地修改栈。
示例 1: 输入:A = [2, 1, 0], B = [], C = [] 输出:C = [2, 1, 0]
示例 2: 输入:A = [1, 0], B = [], C = [] 输出:C = [1, 0]
提示: A 中盘子的数目不大于 14 个。
汉诺塔问题是一个非常经典的递归问题,简单的说,若我们要实现圆盘在不同的柱子间进行移动,那我们就需要合理的利用各根柱子。 根据起始柱 A 上的圆盘数的不同,其移动的方式也会有所区别:

先将最上面的圆盘移动到柱 B

完成移动后,此时柱 A 上就只剩下了一个圆盘,这时又回到了 N == 1 的情况,这时我们只需要按照 N == 1 的处理方法执行即可

最后我们再将柱 B 上的圆盘移动到柱 C 上即可;


再将最后一个圆盘从柱 A 直接移动到柱 C

最后再借助柱 A 将柱 B 上的两个圆盘移动到柱 C


再将柱 A 上的最后一个圆盘移动至柱 C

最后再借助柱 A 将柱 B 上的三个圆盘移动到柱 C

从这里我们不难看出,当我们将柱 A 上的圆盘分为两部分:前 n - 1 个圆盘以及第 n 个圆盘后,整个移动过程我们可以总结为三步:
汉诺塔的函数名我们可以直接使用 Hnt ; 函数的参数我们根据思路分析可以得出参数至少需要4个:
srcmiddesnum汉诺塔函数要实现的功能可以总结为:
num 个圆盘借助 mid 从 src 柱移动到 des 柱因此汉诺塔并不需要任何返回值,即其函数的返回类型为 void:
void Hnt(int* src, int* mid, int* des, int num){
}从解题思路中我们不难看出,当 A 柱上的圆盘数量只有 1 个时,函数只需要执行一步操作——将圆盘从柱 A 移动到柱 C。 若我们将该情况做一个简单的处理——将 N == 1 的情况视为两部分:
此时我们同样执行的是三步:
那此时我们又可以得出一个结论:
N == 0 时,函数不执行任何操作因此,这里我们直接以 num == 0 作为递归基:
if (num == 0) {
return;
}函数的递进关系同样由 num 决定,这里我们可以将 num 分为两部分:
num - 1 个圆盘num 个圆盘在移动的过程中,我们将这两部分的圆盘需要完成三次移动:
num - 1 个圆盘借助 des 柱从 src 柱移动到 mid 柱num 个圆盘从 src 柱移动到 des 柱num - 1 个圆盘借助 src 柱从 mid 柱移动到 des 柱其对应的代码为:
Hnt(src, des, mid, num - 1);
move(src, des, 1);
Hnt(mid, src, des, num - 1);具体的移动过程我们可以通过删除与添加操作完成:
src 删除该圆盘desvoid move(int* src, int* des, int num) {
Insert(des, src[0]);
Delete(src, src[0]);
}现在我们对汉诺塔问题的递归算法整体框架就已经完成了:
void move(int* src, int* des, int num) {
Insert(des, src[0]);
Delete(src, src[0]);
}
void Hnt(int* src, int* mid, int* des, int num) {
if (num == 0) {
return;
}
Hnt(src, des, mid, num - 1);
move(src, des, 1);
Hnt(mid, src, des, num - 1);
}接下来为了能够更好的完成该算法,我们需要对其仅一下整体的优化; 数据结构优化 汉诺塔的实际操作过程是以 栈 完成,因此在不改变原数组的情况下,我们可以通过栈顶指针来指向 A/B/C 这三个栈的栈顶元素;
ASize 指向栈 A 的栈顶元素的下一个元素BSize 指向栈 B 的栈顶元素的下一个元素CSize 指向栈 C 的栈顶元素的下一个元素函数参数优化
函数的具体参数我们可以直接采用 leetcode 中提供的参数:
int* A:栈 A 的数组空间int ASize:栈 A 的栈顶指针int* B:栈 B 的数组空间,需要主动申请内存空间int BSize:栈 B 的栈顶指针int** C:栈 C 的数组空间,需要主动申请内存空间int *CSize:栈 C 的栈顶指针int num:移动的元素个数移动优化 在移动函数中,我们是通过对原栈的栈顶元素进行出栈,对目标栈的栈顶元素进行入栈:
void move(int* A, int* ASize, int* C, int* CSize) {
C[*CSize] = A[*ASize - 1];
*CSize += 1;
*ASize -= 1;
}最后我们将完成了优化后的内容进行组合,就得到了最终的代码:
void move(int* A, int* ASize, int* C, int* CSize) {
C[*CSize] = A[*ASize - 1];
*CSize += 1;
*ASize -= 1;
}
void Hnt(int* A, int* ASize, int* B, int* BSize, int* C, int* CSize, int num) {
if (num == 0) {
return;
}
Hnt(A, ASize, C, CSize, B, BSize, num - 1);
move(A, ASize, C, CSize);
Hnt(B, BSize, A, ASize, C, CSize, num - 1);
}
void hanota(int* A, int ASize, int* B, int BSize, int** C, int* CSize) {
B = (int*)calloc(ASize, sizeof(int));
assert(B);
*C = (int*)calloc(ASize, sizeof(int));
assert(*C);
*CSize = 0;
Hnt(A, &ASize, B, &BSize, *C, CSize, ASize);
free(B);
B = NULL;
}下面我们就在 leetcode 中对代码进行提交测试:

可以看到,此时我们就很好的通过递归解决了汉诺塔问题;
通过本次对汉诺塔问题的深入剖析与实战编码,我们成功地将递归理论应用于具体问题解决中。让我们回顾一下本次学习的重要收获: 🎯 核心收获
💡 递归的威力 汉诺塔问题完美展示了递归算法的优雅与强大——仅仅十余行代码就能解决看似复杂的多层圆盘移动问题。这正是递归的魅力所在:用简洁的代码表达复杂的逻辑。 🚀 下一步学习建议 掌握了汉诺塔这个经典案例后,建议你可以继续探索:
递归作为算法设计的基石,其重要性不言而喻。希望本次实战能帮助你建立对递归的直观感受和深刻理解,为后续的算法学习打下坚实基础。 互动与分享
感谢您的耐心阅读! 关注博主,不错过更多技术干货。我们下一篇再见!