当前位置: 首页 > news >正文

【算法】递归算法实战:汉诺塔问题详解与代码实现

递归的应用

  • 导读
  • 一、面试题 08.06.汉诺塔问题
    • 1.1 题目介绍
    • 1.2 解题思路
    • 1.3 编写代码
      • 1.3.1 定义函数
      • 1.3.2 寻找递归基
      • 1.3.3 寻找递进关系
      • 1.3.4 组合优化
    • 1.4 代码测试
  • 结语

递归算法

导读

大家好,很高兴又和大家见面啦!!!

在上一篇内容中,我们系统学习了递归这一重要算法思想的核心要点:

  • 核心概念分而治之——将复杂问题分解为规模更小、形式相同的子问题

  • 实现方法四步法——定义函数→寻找递归基→建立递进关系→组合优化

  • 分析技巧递归树——直观理解执行过程和计算复杂度的有力工具

理论的价值在于指导实践。今天,我们将通过经典的汉诺塔问题,将递归知识付诸实战应用,检验学习成果并提升算法设计能力。

无论你是希望巩固递归这一关键技术点,还是准备应对算法面试挑战,本次实战都将为你提供宝贵的学习经验。

让我们一同开启这段递归思维的深度训练,体验算法设计的精妙之处!

一、面试题 08.06.汉诺塔问题

1.1 题目介绍

相关标签:递归、数组
题目难度:简单
题目描述
在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。
一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。
移动圆盘时受到以下限制:

  1. 每次只能移动一个盘子;
  2. 盘子只能从柱子顶端滑出移到下一根柱子;
  3. 盘子只能叠在比它大的盘子上。

请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。
你需要原地修改栈。

示例 1
输入:A = [2, 1, 0], B = [], C = []
输出:C = [2, 1, 0]

示例 2
输入:A = [1, 0], B = [], C = []
输出:C = [1, 0]

提示
A 中盘子的数目不大于 14 个。

1.2 解题思路

汉诺塔问题是一个非常经典的递归问题,简单的说,若我们要实现圆盘在不同的柱子间进行移动,那我们就需要合理的利用各根柱子。

根据起始柱 A A A 上的圆盘数的不同,其移动的方式也会有所区别:

  • N == 1 时,即柱 A A A 上有一个圆盘:则我们可以直接将柱 A A A 上的圆盘移动到柱 C C C

汉诺塔1

  • N == 2 时,即柱 A A A 上有两个圆盘:我们需要借助柱 B B B 完成将圆盘从柱 A A A 移动到柱 C C C

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

汉诺塔2

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

汉诺塔3

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

  • N == 3 时,即柱 A A A 上有三个圆盘:则我们需要先借助柱 C C C 将最上面的两个圆盘从柱 A A A 移动到柱 B B B

汉诺塔5

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

汉诺塔6

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

汉诺塔7

  • N == 4 时,即柱 A A A 上有四个圆盘:先借助柱 C C C 将最上面的三个圆盘从柱 A A A 移动到柱 B B B

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

汉诺塔9

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

从这里我们不难看出,当我们将柱 A A A 上的圆盘分为两部分:前 n − 1 n - 1 n1 个圆盘以及第 n n n 个圆盘后,整个移动过程我们可以总结为三步:

  • 借助柱 C C C 将前 n − 1 n - 1 n1 个圆盘从柱 A A A 移动到柱 B B B
  • 将第 n n n 个圆盘从柱 A A A 移动到柱 C C C
  • 借助柱 A A A 将前 n − 1 n - 1 n1 个圆盘从柱 B B B 移动到柱 C C C

1.3 编写代码

1.3.1 定义函数

汉诺塔的函数名我们可以直接使用 Hnt

函数的参数我们根据思路分析可以得出参数至少需要4个:

  • 起始柱:src
  • 过渡柱:mid
  • 目标柱:des
  • 移动的圆盘数:num

汉诺塔函数要实现的功能可以总结为:

  • num 个圆盘借助 midsrc 柱移动到 des

因此汉诺塔并不需要任何返回值,即其函数的返回类型为 void

void Hnt(int* src, int* mid, int* des, int num){}

1.3.2 寻找递归基

从解题思路中我们不难看出,当 A A A 柱上的圆盘数量只有 1 1 1 个时,函数只需要执行一步操作——将圆盘从柱 A A A 移动到柱 C C C

若我们将该情况做一个简单的处理——将 N == 1 的情况视为两部分:

  • 第一部分为前 0 0 0 个圆盘
  • 第二部分为第 1 1 1 个圆盘

此时我们同样执行的是三步:

  • 将前 0 0 0 个圆盘借助 C C C 柱从 A A A 柱移动到 B B B
  • 将第 1 1 1 个圆盘从 A A A 柱移动到 B B B
  • 将前 0 0 0 个圆盘借助 A A A 柱从 B B B 柱移动到 C C C

那此时我们又可以得出一个结论:

  • N == 0 时,函数不执行任何操作

因此,这里我们直接以 num == 0 作为递归基:

if (num == 0) {return;
}

1.3.3 寻找递进关系

函数的递进关系同样由 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 删除该圆盘
  • 将该圆盘添加到 des
void move(int* src, int* des, int num) {Insert(des, src[0]);Delete(src, src[0]);
}

1.3.4 组合优化

现在我们对汉诺塔问题的递归算法整体框架就已经完成了:

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;
}

1.4 代码测试

下面我们就在 leetcode 中对代码进行提交测试:

汉诺塔代码测试
可以看到,此时我们就很好的通过递归解决了汉诺塔问题;

结语

通过本次对汉诺塔问题的深入剖析与实战编码,我们成功地将递归理论应用于具体问题解决中。让我们回顾一下本次学习的重要收获:

🎯 核心收获

  • 递归思维的应用:通过"分而治之"思想,将复杂的汉诺塔问题分解为可管理的子问题

  • 四步法的实战验证:从函数定义到递归基确定,再到递进关系建立,最后进行组合优化,完整展现了递归算法的构建过程

  • 问题抽象能力提升:学会了如何将实际问题转化为递归模型,这是算法设计的关键技能

💡 递归的威力

汉诺塔问题完美展示了递归算法的优雅与强大——仅仅十余行代码就能解决看似复杂的多层圆盘移动问题。这正是递归的魅力所在:用简洁的代码表达复杂的逻辑

🚀 下一步学习建议

掌握了汉诺塔这个经典案例后,建议你可以继续探索:

  • 快速幂算法(pow(x, n))​ - 体验递归在数学计算中的高效应用

  • 其他递归经典问题(如斐波那契数列、二叉树遍历等)

  • 递归的时间复杂度分析方法

  • 递归与迭代的转换技巧

递归作为算法设计的基石,其重要性不言而喻。希望本次实战能帮助你建立对递归的直观感受和深刻理解,为后续的算法学习打下坚实基础。

互动与分享

  • 点赞👍 - 您的认可是我持续创作的最大动力

  • 收藏⭐ - 方便随时回顾这些重要的基础概念

  • 转发↗️ - 分享给更多可能需要的朋友

  • 评论💬 - 欢迎留下您的宝贵意见或想讨论的话题

感谢您的耐心阅读! 关注博主,不错过更多技术干货。我们下一篇再见!

http://www.dtcms.com/a/585907.html

相关文章:

  • js 网站首页下拉广告南宁市网站开发建设
  • SolarEdge和英飞凌合作开发人工智能数据中心
  • asp.net core webapi------3.AutoMapper的使用
  • CCF LMCC人工智能大模型认证 青少年组 第一轮样题
  • 百度搜索不到asp做的网站全球知名购物网站有哪些
  • Android Studio 中 Gradle 同步慢 / 失败:清理、配置全攻略
  • Makefile极简指南
  • 信息系统项目管理师--论文case
  • win7 iis网站无法显示该页面网站上线准备
  • 华为防火墙基础功能详解:构建网络安全的基石
  • 北京网站定制设计开发公司宁波专业定制网站建设
  • 网站的后台怎么做调查问卷设计之家广告设计
  • WebRtc语音通话前置铃声处理
  • 使用XSHELL远程操作数据库
  • 淘宝客网站域名宜昌做网站哪家最便宜
  • 微信小程序中使用 MQTT 实现实时通信:技术难点与实践指南
  • Java computeIfAbsent() 方法详解
  • 做网站市场报价免费企业网站开源系统
  • 天元建设集团有限公司企业代码东莞做网站seo
  • Web前端摄像头调用安全性分析
  • 绵阳网站建设怎么做免费查公司
  • std之list
  • 前端:前端/浏览器 可以录屏吗 / 实践 / 录制 Microsoft Edge 标签页、应用窗口、整个屏幕
  • 做网站像美团一样多少钱中国最新军事消息
  • 软件项目管理实验报告(黑龙江大学)
  • 网络建设需求台州做网站优化
  • PostgreSQL一些概念特性
  • 宁夏建设厅网站6青岛网站建设公司好找吗
  • 社交营销可以用于网站制作行业吗怎样做建网站做淘客
  • 玩转Rust高级应用 如何让让运算符支持自定义类型,通过运算符重载的方式是针对自定义类型吗?