【数据结构与算法】汉诺塔问题(C++)

【数据结构与算法】汉诺塔问题(C++)

目录

一、汉诺塔问题基础定义

1. 问题描述

2. 核心规律

3. 问题分解思想(递归核心)

二、递归实现汉诺塔(简洁优雅,易理解)

1. 递归思路

2. 完整递归代码(带步骤打印 )

三、非递归实现汉诺塔

非递归思路:

1. 非递归思路

2.代码实现:

四、写在最后


汉诺塔(Hanoi Tower)是经典的递归算法入门问题,源于古印度的数学传说,其核心是通过有限次移动,将一根柱子上的所有圆盘按 “小圆盘在上、大圆盘在下” 的规则移到另一根柱子,且移动过程中始终遵守 “一次仅移一个圆盘、大圆盘不能压小圆盘” 的规则。本文将从问题原理出发,详细讲解汉诺塔的递归实现(核心思路,简洁优雅)和非递归实现(基于栈模拟,深入理解递归本质),并提供完整可运行的 C++ 代码,附详细注释和测试案例。一文带你搞懂汉诺塔问题。

一、汉诺塔问题基础定义

1. 问题描述

有 3 根柱子(记为 A、B、C)和 n 个大小不同的圆盘,初始时所有圆盘按从小到大的顺序叠在 A 柱(源柱),要求将所有圆盘移到 C 柱(目标柱),B 柱作为辅助柱,移动过程需满足:

  1. 每次只能移动最顶端的一个圆盘;
  2. 移动过程中,任意一根柱子上的圆盘都必须保持小圆盘在上、大圆盘在下
  3. 仅能在 3 根柱子之间移动圆盘。

2. 核心规律

对于 n 个圆盘的汉诺塔问题,最少移动次数为 2n−1

n=1 时,仅需 1 次移动(A→C)n=2 时,需 3 次移动(A→B、A→C、B→C)n=3 时,需 7 次移动以此类推,每增加 1 个圆盘,移动次数翻倍加 1

3. 问题分解思想(递归核心)

汉诺塔的核心是分治思想,将 n 个圆盘的复杂问题分解为 3 个简单子问题:

  1. 将 A 柱上的前 n-1 个圆盘从 A 柱(源)移到 B 柱(辅助),以 C 柱为临时辅助;
  2. 将 A 柱上剩余的 第 n 个圆盘(最大圆盘)从 A 柱移到 C 柱(目标);
  3. 将 B 柱上的n-1 个圆盘从 B 柱(源)移到 C 柱(目标),以 A 柱为临时辅助。

当 n=1 时,直接移动即可(递归终止条件),无需再分解。


二、递归实现汉诺塔(简洁优雅,易理解)

递归实现是汉诺塔最直观的写法,完全遵循 “分治分解” 的思想,代码量极少,核心是通过函数自身调用实现子问题的解决。

1. 递归思路

  • 函数参数:n(待移动的圆盘数)、A(起始盘)、B(中介盘)、C(目标盘);
  • 终止条件:当n == 1时,直接输出从源柱到目标柱的移动步骤,返回;
  • 递归调用:按 “分解思想” 依次调用自身,解决 n-1 个圆盘的移动问题。

1.首先n=n时,把A杆上的圆盘分为1与n-1两部分

2.因为要把n-1那一部分放到B杆,1那一部分放到C杆,所以,将A杆作为初始杆,B杆作为目标杆,C杆作为中介杆,进行递归。

3.当前C杆上有一个,B杆上n-1个,A杆为空,再次递归,将B杆作为起始杆,把n-1个放到C杆,A杆作为中介杆。以此类推,不断递归下去,

2. 完整递归代码(带步骤打印 )

#include<iostream> using namespace std; int n; void hanoi(char a,char b,char c,int n){ if(n>0){ hanoi(a,c,b,n-1);//将A盘分为两部分,n-1与1,把n-1放到B盘(修改为目标盘),c盘作为中介盘 cout<<n<<" from: "<<a<<" to "<<c<<endl; hanoi(b,a,c,n-1);//将B盘中的n-1作为起始盘移动到目标盘C //不断递归 } } int main(){ cin>>n; char a='A';//A盘 char b='B';//B盘 char c='C';//C盘 hanoi(a,b,c,n);//将A作为起始盘,B作为中介盘,C作为目标盘,n为移动目标数量 return 0; } 

三、非递归实现汉诺塔

递归的本质是 “系统栈自动处理调用过程”,因此非递归实现的核心是栈系统实现递归栈,手动维护汉诺塔的移动状态(待移动的圆盘数、源柱、辅助柱、目标柱)。

非递归思路:

一位美国学者发现:所有的汉诺塔移动可以总结为重复的两步。我们假设现在最小的圆盘在 A 杆上,杆为 A,B,C:第一步:将最小圆盘移动到下一个杆上;第二步:对剩下两个杆的顶上元素进行判断,把较小的那个圆盘移动到较大的那个圆盘上(如果有空杆则移在空杆上)。

重复上述两步就可以得到答案。

注意:这样得到的最后的答案不一定是摞在 C 杆上,如果 N 是奇数则将摞在 B 上。所以如果 N 是奇数我们把杆的摆放顺序设置为 A、C、B,这样就能保证盘子是从 A 杆全部移动到 C 杆。

1. 非递归思路

  1. 定义栈结构,初始化A盘,从大到小排序好;
  2. 判断n的奇偶性,做出对应的转化;
  3. 按照第一步:将最小圆盘移动到下一个杆上;第二步:对剩下两个杆的顶上元素进行判断,把较小的那个圆盘移动到较大的那个圆盘上(如果有空杆则移在空杆上)进行循环操作;
  4. 循环直至栈B或栈C全满,所有圆盘移动完成。

视频讲解可以看一下这个:——>视频讲解直达<——



2.代码实现:

#include<iostream> #include<stack> using namespace std; int n; char s[5]={'0','A','B','C'};//杆A、B、C名称 stack<int> a[5];//栈实现杆的存储 void move(int now,int next){//把当前杆的最上面一个移动到下一个杆上 a[next].push(a[now].top()); printf("%d from %c to %c\n",a[now].top(),s[now],s[next]);//输出移动的起始目标 a[now].pop(); } int main(){ cin>>n; int count=0; for(int i=0;i<n;i++)a[1].push(n-i);//从大到小初始化A杆 if(n%2==1){//如果是奇数个圆盘交换B盘与C盘的功能 s[2]='C'; s[3]='B'; } while(1){ int next; for(int i=1;i<=3;i++){//循环移动判断三个杆 if(!a[i].empty()){//如果不为空 if(a[i].top()==1){//且最上面是最小的盘 if(i==3)next=1;//若是最后一个杆,它的下一个杆是A杆 else next=i+1;//否则正常i+1是下一个 move(i,next);//当前杆的盘移动到下一个杆 break; } } } if(a[2].size()==n||a[3].size()==n)break;//如果都在一个杆上了就完成了,判断a[2]与a[3]因为奇数的目标杆是2号杆,偶数的目标杆是3号杆 int other1,other2; switch(next){//对剩下的两个杆进行第二步操作 case 1:{//如果此次对1号杆进行了操作 other1=2;//剩下两个杆就是2号3号杆 other2=3; break; } case 2:{//依次类推 other1=3; other2=1; break; } case 3:{ other1=1; other2=2; break; } } if(a[other1].empty())move(other2,other1);//如果other1为空,那它是最小的一个杆,将other2移动到other1 else if(a[other2].empty())move(other1,other2);//如果other2为空,那它是最小的一个杆,将other1移动到other2 else{ if(a[other1].top()<a[other2].top())move(other1,other2);//否则判断大小,将小的移动到大的杆上 else move(other2,other1); } } } 

四、写在最后

本文的 C++ 非递归实现基于栈递归的核心思想,结合 C++ 面向对象和标准库的优势,实现了内存安全、无栈溢出、高效率、易维护的汉诺塔解法,相比 C 语言版和 C++ 递归版更适合实际工程应用。

通过学习该实现,不仅能掌握汉诺塔的非递归解法,更能理解递归的本质(系统栈的自动调用)和递归转非递归的通用方法(自定义栈模拟状态),这一思路可推广到所有递归问题(如二叉树遍历、深度优先搜索 DFS、归并排序等)的 C++ 非递归实现中。

执笔至此,感触彼多,全文将至,落笔为终。

Read more

解决Markdown笔记图片失效问题:Gitee+PicGo图床搭建全攻略

解决Markdown笔记图片失效问题:Gitee+PicGo图床搭建全攻略

引言:为什么要解决搭建图床? 你是否遇到过这样的场景: * 用 Obsidian 写了半年的知识库,换电脑时发现 所有图片都变成 “破碎图标”; * 把 Markdown 笔记分享给同事,对方打开后 图片全是本地路径,根本看不到内容; * 尝试用云盘链接替代,却因为 “防盗链” 或 “链接过期”,图片还是无法正常显示…… 本地 Markdown 笔记的 “图片依赖本地路径”,是困扰无数创作者的痛点。而解决这个问题的核心,就是搭建一个 “图床” —— 把图片托管到云端,让链接永远有效。 本文将带你用 “Gitee(国内免费仓库)+ PicGo(自动上传工具)+ Node.js(运行环境)” 搭建图床,不仅解决 “图片失效”,还能实现: * ✔️ 国内访问快:Gitee 服务器在国内,无需科学上网,图片秒加载; * ✔️ 完全免费:Gitee

By Ne0inhk

Qwen3-ASR-1.7B开源可部署:提供SDK封装,支持Java/Node.js调用

Qwen3-ASR-1.7B开源可部署:提供SDK封装,支持Java/Node.js调用 语音识别新选择:Qwen3-ASR-1.7B让多语言语音转文字变得简单高效,完全离线运行,保护你的数据隐私 1. 为什么选择Qwen3-ASR-1.7B? 如果你正在寻找一个既强大又易用的语音识别解决方案,Qwen3-ASR-1.7B值得你重点关注。这个模型最大的特点是开箱即用——不需要复杂的配置,不需要联网依赖,下载就能用。 想象一下这样的场景:公司内部的会议录音需要快速转成文字,但内容涉及敏感信息,不能上传到云端。这时候,一个完全离线的语音识别方案就显得尤为重要。Qwen3-ASR-1.7B正是为此而生,它能在你的本地服务器上运行,数据完全不出公司网络,同时支持中、英、日、韩等多种语言。 更让人惊喜的是,这个模型提供了完整的SDK封装,意味着你不仅可以通过网页界面使用,还能用Java、Node.js等编程语言直接调用,轻松集成到现有的业务系统中。 2. 快速上手:5分钟部署体验 2.1 环境准备与部署 让我带你快速体验一下这个模型的部署和使用过程。整个过程非常简单,

By Ne0inhk
Git 分支管理完全指南:从基础到团队协作

Git 分支管理完全指南:从基础到团队协作

🔥个人主页:Cx330🌸 ❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》 《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔 《Git深度解析》:版本管理实战全解 🌟心向往之行必能至 🎥Cx330🌸的简介: 目录 前言: 一、为什么要分支?——分支的意义 二. Git 分支基础:核心概念与常用命令 2.1 分支与 HEAD 指针解析 2.2 基础指令:查看、创建、切换分支 三. Git 分支进阶:合并、删除和冲突 3.1 合并分支(git merge 分支名) 3.2 删除分支(

By Ne0inhk

【GitHub项目推荐--CapCutAPI:开源剪映/Jianying API 解决方案】

简介 CapCutAPI 是一个强大的开源编辑API,让开发者能够通过编程方式完全控制AI生成的媒体资产,包括图像、音频、视频和文本。该项目由Sun Guannan创建,旨在解决AI视频生成中常见的控制不足问题,提供精确的后期编辑和定制能力。无论是调整视频速度、镜像图像,还是添加复杂的特效和动画,CapCutAPI都能帮助您轻松将创意想法转化为精美的视频内容。 🔗 GitHub地址 : https://github.com/sun-guannan/CapCutAPI ⚡ 核心价值 : 程序化视频编辑 · 云端预览 · 多格式支持 · 开源免费 主要功能特性 1. 核心架构概览 2. 核心功能矩阵 功能模块 支持程度 详细功能 草稿管理 ✅ 完全支持 创建、保存、导入、导出剪映草稿文件 视频处理 ✅ 完全支持 多格式导入、剪辑、转场、特效应用 音频编辑 ✅ 完全支持 音轨管理、音量控制、

By Ne0inhk