汉诺塔问题
汉诺塔问题
学递归,跳不过汉诺塔这个程序。以前弄NOIP,老师很详细地讲过汉诺塔的原理以及实现算法,不过我上大学了却发现老师讲到汉诺塔,只是像一笔带过,原理都没讲通,更别说算法了。我相信像他那么讲,没一个同学(没基础的)能弄得懂,就算你给一个flash汉诺塔的游戏,也不见得会玩。
汉诺塔真的挺有意思的,我写这篇文章,也算是回忆回忆以前学过的知识。如果有什么错误,还请原谅。
没有听说过汉诺塔的人,可以去baidu查查,或则你去http://www.4399.com/flash/293.htm 玩一玩,大概就知道是干什么的了。
现在有A、B、C三根柱子,A柱子上有n个大小不同的盘子,准备移到C柱子上。我们现在换一个说法:A柱子上有n个大小不同的盘子,我们借助B,将A上的n个盘子移动到C上。
假设n是1,很简单,直接将A上的1个盘子移到C上。
假设n是2,怎么想?分三步,一是将小盘子移到B上,二是将大盘子移到C上,三是将小盘子移到C上。在大盘子移到C上之前,小盘子在哪里都无所谓。
所以我们先将小盘子从A上移到B上,再把大盘子从A移到C上,再把小盘子从B移到C上。移完后总共需要移动的次数是3。
假设n是3,分三步,一是把上面的两个盘子借助C移到B,二是把大盘子移到C,三是将其余两个盘子借助A移到C。其中第一、第三步又分两个小步。这两步步骤和n=2时相同,所以移完后总共需要移动的步数是3+1+3=7步。
我们已经可以从其中发现递归的思想。当我们做第一步时,完全可以忽略最大的盘子,问题仅仅是将两个盘子从A借助C移到B。第三步时,也忽略已经移到C柱子上的大盘子,问题变成将在B上的两个盘子借助A移到C。
于是我们可以设计一个函数,它的功能是将n个在x柱子上的盘子借助y柱子移到z柱子上。
这么写:hanota(n,x,y,z);
于是我们上面的三步可以用程序语言来表达:
- hanota(n-1,A,C,B);
- hanota(1,A,B,C);
- hanota(n-1,B,A,C);
这是三个盘子时候的情况。四个盘子时候我们仍然可以这样想,先将上面的三个盘子借组C移动到B,再将最下面一个盘子移动到C,最后将其余三个盘子借助A移动到C。然后一、三两步又分两个小步。通过递归的思想,将大问题逐步转化成小问题。最后解决。
下面看我们这个hanota函数怎么写。
#include<stdio.h>
void hanota(int n,char x,char y,char z){
if(1 == n)
printf("将%c上的盘子移动到%c柱子上n",x,z);
else{
hanota(n-1,x,z,y); //第一步
printf("将%c上的盘子移动到%c柱子上n",x,z); //第二步
hanota(n-1,y,x,z); //第三步
}
}
int main(){
int n;
printf("输入盘子的数量:");
scanf("%d",&n);
hanota(n,'A','B','C');
return 0;
}
我们看这个递归,最后的结束条件是n==1时。也就是说问题大而化小之后,只有一个盘子了,就可以直接移动了。否则就继续将n-1放进hanota函数进行移动(也是三步)。
这是移动三个盘子的结果图,和我上面的那个gif里移动的步骤是一样的。你们也可以试一下,不过输入的n不能太大了,否则算不出来的。移动的步数和n是成指数增加的,当n=64时,需要移动2<sup>64</sup>-1次,这是个天文数字。n通常不要大于16就行了。
如果你以前没有任何递归的基础,也许你看不懂这些代码。如果你知道递归的一些知识,你才会发现这道题实际上十分基础。但是如果不用递归来做,我想没多少人能把程序写出来。因为递归解决了我们很难思考的问题,将问题大而化小。很多新手包括我有时候在想,递归明明好像比循环难,但为什么老师说递归更易于理解。这些东西也许只有等我们做了更多题,接触了更多有关树和图的问题以后才能理解吧。
最后给大家和我自己留一个问题:汉诺塔是三根柱子,如果我们有四根柱子,我们又怎样移动盘子,或者说怎样移动使步数最少?有时间我会想想这个问题,以后写一个“汉诺塔拓展”。
我把程序传到附件里了,大家可以下载运行了试试。
- 【译】Spring 官方教程:使用 Restdocs 创建 API 文档
- c#中设置快捷键
- 程序员你为什么这么累【续】:编码习惯之工具类规范
- IntelliJ IDEA 复杂的重构技巧
- 打造属于自己的支持版本迭代的Asp.Net Web Api Route
- 分布式消息队列 RocketMQ 源码分析 —— Message 拉取与消费(上)
- 分布式消息队列 RocketMQ 源码分析 —— Message 拉取与消费(下)
- Spring Boot中使用RabbitMQ
- Spring Cloud构建微服务架构:消息驱动的微服务(入门)【Dalston版】
- 哪类人适合当产品经理?
- 产品经理·杂谈
- Python机器学习中的特征选择
- Android学习第八弹之改变状态栏的颜色使其与APP风格一体化
- 手把手教你 MongoDB 的安装与详细使用(二)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- http常见状态码
- golang time相关总结
- [白话解析] 深入浅出熵的概念 & 决策树之ID3算法
- [白话解析] 深入浅出边缘计算
- 变废为宝 | 旧电脑变云数据中心 | Esxi 配置
- 【Java面试总结】Java基础(上篇)
- Esxi安装LEDE(OpenWrt) | 旧电脑变庞路由
- [白话解析] 深入浅出贝叶斯定理
- LEDE(OpenWrt)安装openclash |旁路由技巧 | 配置网络流量全部通过旁路由
- [白话解析] 深入浅出朴素贝叶斯模型原理及应用
- 【Java面试总结】Java基础(下篇)
- 使用现代化的脚本进行 ArcGIS JS API 开发
- TextField suffixIcon点击时TextField 选中弹出键盘<Flutter Bug篇>
- seata redis模式重构之全局事务更新
- 【STM32F407】第7章 RL-TCPnet V7.X网络协议栈移植(RTX5)