• 中文
    • English
  • 注册
  • 查看作者
  • 自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    一.  前言

    汉诺塔问题从我们初学C语言的时候,就已经接触到

    但是大多数人基本上只是以背代码的方式了结了这个问题,并没有彻底的搞懂,我便是其中一个

    最近学数据结构的时候又遇到了这个问题,便想着顺水推舟,解决这个问题

    在网上查阅了各种资料,大多数都讲的含糊不清,干脆自己写一份

    下面我们通过自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    因为所有文字均为手打,又整理的比较仓促

    难免出现错别字或者思路错误的地方。

    欢迎各路大神指点批评

    如果一遍看不懂没有关系,一个步骤一个步骤的多看几遍就可以了(要有耐心ヾ(゚∀゚ゞ))

    为了表示方便,我们用以下方式代表相应的自然语言:

    箭头 —>表示移动的意思,且一次只移动一个。

    比如A —> C代表将A柱当前最上面的1个盘子移动到C

    A(n)代表A柱中当前有n个盘子

    二.  汉诺塔问题简介

    假设有三根标号为A,B,C的柱子,A柱子上从下到上按金字塔状叠放着n个不同大小的圆盘,要把所有盘子移动到柱子C上,并且每次只能移动一个,移动过程中不能把大盘子放在比自己小的盘子上面,请问至少需要多少次移动多少次才能完成?

    三.  文字解答

    其实无论n等于几,我们都可以将n分为两种情况:

    第一种:n = 1 的时候,只有一个盘子,直接移动就可以

    第二种:n > 1 的时候,因为一次只能移动一个盘子,所以我们需要借助其他柱子来移动

    这里我们可以看出本题符合递归的条件了,第一种情况自然很容易实现

    难就难在第二种情况,如何将第二种情况实现呢?

    其实当n >  1 的时候,无论n等于几,也无论一共移动了多少次,汉诺塔一定会出现三种状态:

    状态一:将n – 1 个圆盘从开始柱,移动到中转柱,此时A(1),B(n – 1),C(0)

    状态二:将开始柱中剩余的那一个最大圆盘,移动到目标柱,此时A(0),B(n – 1),C(1)

    状态三:将中转柱中n – 1个圆盘移动到目标柱,移动完成,,此时A(0),B(0),C(n)

    (ps. 也就是说这是三个大的步骤,每个步骤再细分成小的操作才完成。 状态二自然好说,只有一个盘子,直接移动就可以了,但是状态一和状态三,是如何将n – 1 个盘子全部移动到其他柱子上的呢?先不要着急,这里我们先不用管是如何实现的,只需要了解到这样的一个解题思路,具体的实现我们在本文的第六节中我们会详细的解答)

    我们可以看出,第一部分的操作和第三部分的操作是类似的,都是将n – 1个盘子,移动到其他柱子上面

    所以第一部分和第三部分的移动次数是相同的

    我们假设总移动次数为Hn

    则将n-1个盘子从A移动到B所需次数为H(n – 1)

    那么Hn = H(n- 1) + H(n- 1) + 1

    =  2^n – 1

    也就是状态一的移动次数+是状态二的移动次数+状态三的移动次数  = 总移动次数

    可能上面的文字可能难以理解,下面我们用图片的形式来表达

    四.  图形解答

    这里我们以n =  4 为例,n > 1,符合第二种情况,则进入三个状态:

    状态一:将n – 1 个圆盘从开始柱,移动到中转柱

    也就是将A中 n – 1 个盘子全部移动B,此时 A(1) B(4 – 1)C(0)。如下图:

    自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    状态二:将开始柱中剩余的那一个最大的圆盘,移动到目标柱

    也就是将A中剩下的最大的那个盘子,移动到C ,此时A(0) B(4 – 1)C(1)。如下图:

    自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    状态三:将中转柱中n – 1个圆盘移动到目标柱,移动完成

    将B中n – 1 所有盘子全部移动到C,此时A(0) B(0)C(4),移动完成,如下图:

     自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    我们可以发现,无论n 等于多少,总是会在某一步中出现上面的三个状态

    具体的每一步的实现步骤请看动图:

    自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    五.  代码实现

    这里我们设计一个move(int n, char A, char B, char C)方法

    其中n = 第n个圆盘,A = 开始柱,B = 中转柱,C = 目标柱

    我们先从n = 1 看起,n = 1 ,符合第三节中我们提到的第一种情况,则直接将从A —> C 即可

    void move(int n,char A, char B, char C) 
    {
    
    	if(n == 1)
    	{
    	    printf("%c -> %c %d \n",A,C,n);
    	}
    		
    }

    接下来,我们从n = 2 看起,n = 2 > 1,符合第二种情况,我们便采用第二种情况的三个状态来解决

    状态一:将n – 1 个圆盘从开始柱,移动到中转柱

    也就是以C为中转站,将A中 n – 1 = 1个盘子全部移动B。此时 A(1) B(1)C(0)

    move(n-1,A,C,B);

    状态二:将开始柱中剩余的那一个最大的圆盘,移动到目标柱

    也就是将A中剩下的最大的那个盘子,移动到C (由于是直接移动,则省略方法,直接输出),此时 A(0) B(1)C(1)

    printf("%c -> %c %d \n",A,C,n)

    状态三:将中转柱中n – 1个圆盘移动到目标柱,移动完成

    也就是以A为中转站,将B中n – 1 所有盘子全部移动到C,完成,此时 A(0) B(0)C(2)

    move(n-1,B,A,C);

    则总的实现代码为:

    #include<stdio.h>
    
    void move(int n,char A, char B, char C)
    {
    
        if(n == 1)
        {
            printf("%c -> %c %d \n",A,C,n);
        }
        else
        {
            move(n-1,A,C,B);
            printf("%c -> %c %d \n",A,C,n);
            move(n-1,B,A,C);
        }
    
    
    }
    
    void main()
    {
        move(1,'A','B','C');
    }

    六.  详解

    看到这里似乎已经没有什么问题。但是n = 2的时候,我们每次都是只移动一个盘子,就完成了这三个状态

    如果n = 3,状态一我们就需要将2个盘子移到B,如果n =4,状态一我们就需要用将3个盘子移动到B

    如果有n个盘子,状态一我们就需要将n – 1 个盘子移动到B

    那么有多个盘子的时候,我们如何才能完成状态一和状态三呢?

    其实很简单:我们只需要将n 看成n – 1就可以了

    这里我们以n = 3 为例,进行讲解

    (这里我们不再使用A — > B 这种写法,而是带入具体的圆盘代号,比如1 —> B)

    n = 3 时,A为开始柱,B为中转站,C为目标柱

    自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    首先进入n = 3的状态一:将n – 1 个圆盘从开始柱移动到中转柱,对应代码:

    move(n-1,A,C,B);

    此时,我们可以暂时忽略掉3这个圆盘,也就是将n看成n – 1,则n = 2 。

    这个问题便已经成了将A中的1和2这两个盘子都移动到B的问题。如下图:

    (灰色数字代表我们还未移动但是准备移动完成的效果,绿色数字代表我们暂时将这个圆盘忽略)

    自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    我们进入n = 2 的状态一:将n – 1 个圆盘从开始柱,移动到中转注意,此时n = 3 的状态一还未结束

    也就是此时A成了开始柱,B成了目标柱,C成了中转柱

    则将 1 —> C

    n = 2 的状态一完成

    自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    进入n = 2 状态二: 开始柱中剩余的那一个最大的圆盘,移动到目标柱

    则将2 —> B

    n = 2 的状态二完成

    自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    进入n = 2 状态三:将中转柱中n – 1个圆盘移动到目标柱

    也就是将C中 n – 1 = 1个盘子移动到B:

    n = 2 的状态三完成

    自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    则此时n = 2 全部移动完成

    同时,n = 3 的状态一才刚刚完成

    我们回到n = 3,再开始进入n = 3的状态二:开始柱中剩余的那一个最大的圆盘,移动到目标柱。,对应代码:

    printf("%c -> %c %d \n",A,C,n);

    则将3 —> C

    n = 3的状态二完成

    自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    进入n = 3 的状态三:将中转柱中n – 1个圆盘移动到目标柱,对应代码:

    move(n-1,B,A,C);

    则此时我们再次将3忽略

    也就是再次将n 看成n – 1 ,则此时n = 2,则此时B成了开始柱,C成了目标柱,A成了中转柱,

    也就是将B中的1和2这两个圆盘移动到C即可

    自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    我们再一次进入n = 2 时的状态一:将n – 1 个圆盘从开始柱,移动到中转柱

    我们将1 —> A

    n = 2 的状态一完成

    自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    进入n = 2 的状态二:开始柱中剩余的那一个最大的圆盘,移动到目标柱

    则将2 —> C

    n = 2的状态二完成

    自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    进入n = 2 的状态三:将中转柱中n – 1个圆盘移动到目标柱

    则将1 —> C

    n = 2 的状态三完成

    自然语言、图形、代码三种形式,彻底搞懂汉诺塔问题

    此时n = 3 的状态三才刚刚完成,整个移动也完成了

    七.  n = 3总结

    1.  首先看n,若n = 1 ,则直接移动

    2.  若n > 1,则进入三个状态:

               2.1  进入n 的第一个状态

               2.2  将n 看成n – 1,然后进入n – 1 的第一个状态

               2.3  n – 1 完成第三个状态后,进入n 的第二个状态

               2.4  n完成第二个状态后,进入n的第三个状态

               2.5  将n 看成n – 1,然后进入n – 1 的第一个状态

               2.6  n – 1 完成第三个状态后,n的第三个状态也完成

    n 等于其他数字的时候,都按照这个规律来就可以了

     

  • 0
  • 0
  • 0
  • 4.3k
  • 请登录之后再进行评论

    登录
    单栏布局 侧栏位置: