目录

  • 前言
  • 函数递归
  • 什么是汉诺塔问题?
  • 题目
  • 解题思路
    • 1. 移两个盘子
    • 2. 移n个盘子
    • 3.抽象
  • 代码实现
  • 结语

前言

汉诺塔问题是一道经典的计算机科学中的递归算法题,通过解决汉诺塔问题以更好的理解递归。

函数递归

函数递归:函数自己调用自己。

函数递归的两个必要条件:
1.函数自身的调用要有限制条件,如果没有限制条件,就会无限的自己调用自己而导致栈溢出(stack overflow)(关于栈溢出的问题我会再出一篇文章详细分析底层)。
2.函数每一次调用自己时,要逐渐接近限制条件。

函数递归的本质:大事化小。把一个大问题分解成若干个小问题。

什么是汉诺塔问题?

问题的描述如下:

有三根柱子,标记为A、B、C,A柱子上有n个大小不同的盘子,盘子从下到上按照大小递增排列。现在需要将A柱子上的所有盘子移动到C柱子上,移动过程中可以借助B柱子,但是每次只能移动一个盘子,并且大盘子不能放在小盘子上面

起始状态:

目标状态:

移动过程中的违规状态(大盘子在小盘子上面):

题目

请用C语言实现以下功能:输入在A柱子上的盘子数,输出所有盘子从A柱子移动到C柱子的整个过程

解题思路

只要把解题思路搞明白了,无论用什么编程语言,都只是一种思路的不同表示形式而已。

1. 移两个盘子

先从最简单的移两个盘子开始分析:

两个盘子从A柱移到C柱的过程:

  1. 小盘子先从A柱移到B柱
  2. 然后大盘子从A柱移到C柱
  3. 最后小盘子从B柱移到C柱。

过程简写如下:

  1. A–>B
  2. A–>C
  3. B–>C

要想让A柱最底下最大的盘子移到C柱上,只有上面的小盘子移到B柱时,大盘子才能从A柱移到C柱。

2. 移n个盘子

知道怎么移两个盘子之后再看移n个盘子:

移两个盘子和移n个盘子的相同之处:

无论A柱有多少个盘子,想要让A柱最底下最大的盘子移到C柱上,只有这大盘子上面所有的小盘子都移到B柱时,最下面的大盘子才能从A柱移到C柱。
所以我们可以先把上面的n-1个盘子看成一个整体,把这个整体从A柱移到B柱,再把最下面的盘子从A柱移到C柱。
步骤1.把前n-1个盘子组成的整体从A柱移到B柱

步骤2.再把最下面的盘子从A柱移到C柱

步骤3.把前n-1个盘子组成的整体从B柱移到C柱

在不考虑怎么移前n-1个盘子的情况下,上面的这三个步骤和移两个盘子的步骤简直一模一样!

那具体怎么移动上面的n-1个盘子呢?

以步骤3中为例:把前n-1个盘子组成的整体从B柱移到C柱的过程中,这个整体又可以划分为前n-2个盘子组成的整体和第n-1个盘子

想要把第n-1个盘子从B柱移到C柱,得先把上面的前n-2个盘子看成一个整体,这个整体从B柱移到A柱。

发现了吗?我已经正在把一个大问题拆分成若干个小问题了:
刚开始的大问题是在A柱上面的n个盘子从A柱移到C柱,在上面的 步骤1 和 步骤3 中,又可以细分成一些小问题:把n-1个盘子从A柱和B柱,把n-1个盘子从B柱和C柱这些。当然,在移动n-1个盘子的过程中会出现更小的问题。这些无数的小问题们直到遇见 步骤2 的时候,也就是只有一个盘子的时候,不再需要再划分成更小的问题解决,直接移动这1个盘子即可。可能这个时候思路还比较乱,接下来我会在下面一节的抽象中展现完整的思路。

3.抽象


这里可能会有几个疑问:

  1. 每一次从一个柱子移到另一个柱子中,这两个柱子的位置不是固定的,怎么处理这些柱子的对应关系?
  2. 什么时候不会再划分出两个新的函数,也就是说限制条件是什么?

第一个问题回答:我们不能全部知道各种情况什么柱要移到什么柱,但是他们有共同的特点:都是从起始的柱子移到目的地柱子,总有一个柱子是第三者当工具。我们可以称起始的柱子为起始柱,称目的地柱子为目标柱,称第三者为工具柱

A柱,B柱,C柱是固定的,但是他们每次充当的角色可以是不同的 (起始柱,目标柱,工具柱)

第二个问题回答:当n减到1时,也就是只有一个盘子时,按照上面的3个步骤走一遍,发现只需走第二个步骤,因为第一个和第三个步骤是0个盘子从起始柱移到目标柱,没有盘子了就肯定不要执行啦。

所以限制条件是n>0

综上所述:
解决汉诺塔问题的函数需要用到4个变量:

  1. 盘子的个数n
  2. 起始柱
  3. 目标柱
  4. 工具柱

有了步骤,参数还有限制条件,代码的实现就轻松很多了。

代码实现

#include//盘子个数起始柱目标柱工具柱void hanoi(int n, char A, char C, char B){if (n > 0)//限制条件{//第一步,把A柱上面的n-1个盘子移到B柱。此时A柱为起始柱,B柱为目标柱,C柱为工具柱hanoi(n - 1, A, B ,C);//第二步,直接输出A柱最底下最大的盘子移到C柱的过程。此时A柱为起始柱,C柱为目标柱printf("%c-->%c\n", A, C);//第三步,把B柱上面的n-1个盘子移到C柱。此时B柱为起始柱,C柱为目标柱,A柱为工具柱hanoi(n - 1, B, C, A);}}int main(){int n = 0;printf("请输入在A柱上的盘子的个数:>");scanf("%d", &n);char A = 'A';char B = 'B';char C = 'C';hanoi(n,A,C,B);return 0;}

代码运行例子:
把3赋值给变量n:

把4赋值给变量n:

结语

非常感谢您能够阅读完这篇文章,如果有任何建议或纠正欢迎在评论区留言。如果您认为这篇文章对您有所收获,点一个小小的赞就是我创作的巨大动力,谢谢!