不知道学习编程的小伙伴们是否有过和我一样的经历,当我们初学编程时,汉诺塔问题是很难跨越一道坎,不仅因为它使用到了递归,并且和我们初识的递归题(比如说求阶乘,斐波拉契数列项等等)不太一样,在该问题的函数实现中包含了多重递归,中间的逻辑性也不太容易理解。也正因如此,当时因为老师没有要求一定将它研究透而有点小确幸,但其实这样做是不对的,正如一句话所说“当你觉得一条路很艰难的时候,一定是上坡路!”。所以今天就让小风来带着大家一起迎难而上,提升自己的实力吧!
前言
首先很高兴能和各位热爱编程的小伙伴一起解决问题,汉诺塔问题可以说是递归中非常经典的一道问题,我们不管初学哪一门语言时,当学到递归章节或多或少都可能会听说甚至是接触到它,从这我们大概也能猜出它是非常重要的一个问题,虽然我们现在可能初学递归觉得它很难,但我们并不能因此而选择放弃,因为它象征着我们是否真正能否走进递归世界的一道重要标准之一。
OK,咱们进入主题!像很多类似讲解汉诺塔问题的文章直接就将所有的汉诺塔问题都直接一步带过说它一定经过三个过程,当然事实虽然如此,但他们都没有将其中的原理讲的清晰明了,很多时候可能会直接带过。这样通常就会使得我们理解起来感到非常的突兀,那么今天小风就带着大家如何将其中的结论给归纳出来,希望能对大家有所帮助
一、汉诺塔问题
问题描述
在汉诺塔问题当中,设有3根标号为A、B、C的三根柱子,在A柱上放着n个盘子,每一盘子逗比下面的盘子要小一点,要求把A柱上的盘子全部转移到C柱上。并遵循如下移动规则:
- 一次只能移动一个盘子
- 移动过程中大盘子不能放在小盘子上面
- 在移动过程中盘子可以放在A、B、C的任意一个柱子上
输入格式
输入一个整数n,代表盘子的数目
输出格式
输出移动盘子的具体步骤。例如:B->C,即表示把盘子从B柱移到C柱
二、实现代码
#include<stdio.h>
//交换函数
void Swap(char x, char z)
{
printf("%c->%c\n", x, z);
}
//定义递归函数,三步走
void Hanoi(int n, char x, char y, char z) //进入初始函数
{
if (1 == n) //递归出口
{
Swap(x, z);
}
else
{
Hanoi(n - 1, x, z, y); //第一阶段递归,将n-1个盘通过C转到B柱上
Swap(x, z); //第二阶段,将A柱上的最后一个盘换到C柱上
Hanoi(n - 1, y, x, z); //第三阶段,将B上所有的盘转移到C柱上
}
}
int main()
{
int n = 0; //圆盘个数
//定义三根柱子
char a = 'A';
char b = 'B';
char c = 'C';
//输入
printf("请输入圆盘个数:");
scanf("%d", &n);
Hanoi(n, a, b, c);
return 0;
}
三、分析拆解
问题分析
首先当我们拿到一个比较复杂题目的时候,不可能直接就开始做,而是先分析其题目中的内在含义,理清思路并建立框架,才能正确高效的将问题解决,因此我们着手解决汉诺塔问题的时候,很有必要将以下问题想清楚
1.怎么知道盘子必经历这三个步骤?
第一步:将 n-1 个盘子从A移到B柱
第二步:将第那个盘子从A柱移到C柱
第三步:把B住上的 n-1 个盘子移到C柱上
我们看到的各种资源都会说某号盘子借助辅助塔B从当前起点A移动到单签最终点C。那么为什么一定会经过这三个过程呢?让我们通过结合图例一起来归纳总结一下吧!
首先当盘子数目为 n = 1 的时候: 我们只需要将A柱上的盘子直接转移到C柱即可
当盘子数目为 n = 2 的时候:首先将第一个盘子从A柱移到B柱,再将A柱中的最底下的一个盘子从A柱移到C柱,最后再将B柱上的盘子从B柱移到C柱。
当盘子数目为 n = 3 的时候:盘子的移动步骤就不再赘述了,通过观察图例即可。
通过观察上图三个示例图,我们不妨圈出其中所对应的共同之处并进行标记
通过观察不难发现,当盘子数目 n >= 2 时, 都会经理上述我们所总结三个必经步骤,也就说不管盘子数目和变化,必须严格按照三大步骤的顺序进行操作,只有进行完了第一步才能进入下一个环节,那么这就为我们递归寻求到了进行递归的路径,当我们没有完成某一阶段性的操作时,那么就需要我们的递归函数一直进行着这一步的操作,直至完成这一步的所有任务(也即是我们的递归出口)。当我们的盘子数为n时,由浅入深归纳可得如下图所示规律:
这就是我们所得出三个必经过程的由来,虽然这样不如使用科学的数学归纳法证明来的严谨,但我们通过这样总结归纳更加通俗易通,而且我们只需找出其中的规律实现编程即可。
2.汉诺塔函数Hanoi(n, x, y, z)是如何执行的,各个参数的含义是什么?
首先我们得了解其中各个参数所表示的含义
n:表示起始柱上所剩的盘子数
x:表示起始柱
y:表示辅助柱/中间柱
x:表示终止柱
通过上述各参数的描述,我相信大家已经对这个函数所实现的功能大概也猜的差不多了,总结一句话就是:将起始柱 x 上的 n 个盘子通过中间柱 y 转移到最终的 C 柱上。
3.何会这样设计汉诺塔函数?
设计这个函数函数的意义在于它是一个递归函数,通过上述描述的这个函数的功能来看,只要这个函数能一直能够调用自身直至完成他所在的这个阶段(即找到递归出口,还剩最后一个盘),返回并进入下一个阶段,从这大家便也能理解为什么这个递归函数中存在多重循环这么一个原因。
四、代码递归运行的详细过程&正确性检验截图
递归过程详解图例
大家也可以亲自尝试着画一画哈,这样有助于我们对整个递归程序运行逻辑的理解。
正确性检验
当我们输入圆盘个数为3时,通过手动检验可知盘子转移的过程是完全正确的,当然各位小伙伴也可以亲自换另一个数值来进行尝试并检测器正确性啦。
总结
本篇文章小风觉得大家如果能深入理解多重递归调用,将会对我们以后的编程算法分析学习有很大的帮助,例如分治法、动态规划等等一系列很多复杂的算法都是与这种多重递归相关的。
最后希望小风的这篇文章能对大家带来一定的帮助,也希望各位小伙伴们一起加油呀,冲冲冲!