汉诺塔的移动规则

  1. 有三根柱子A,B,C(分别作为开始柱,中转柱,目标柱)。
  2. 开始柱上有若干盘子,每次移动一块盘子,小盘只能叠在大盘的上面
  3. 实现将所有盘子从开始柱全部移动到目标柱上。

汉诺塔的抽象理解

  • 当只有一个盘子时,即移动一步,开始柱->目标柱
  • 当有两个盘子时,需要使用中转柱,先将小盘从开始柱->中转柱,再将大盘从开始柱->目标柱,最后将小盘从中转柱->目标柱
  • 以此类推,当有n个盘子时可以将(n-1)个小盘视为一个整体,此时就简化成了只有两个盘子时的情况。即先将(n-1)个小盘从开始柱->中转柱,再将大盘从开始柱->目标柱,最后将(n-1)个小盘从中转柱->目标柱。盘子数目一次递减调用移动两个盘子时的情况,可用递归方法。

汉诺塔的步骤规律分析

要用程序来解决这个问题,我们先定义一个移动函数:move(移动数,开始柱,中转柱,目标柱),例如 move(2,A,B,C) 表示将2个盘子从A柱(开始柱)借助B柱(中转柱)移动到C柱(目标柱)。关于开始柱,中转柱,目标柱这三个概念可以用移动过程中的某个状态来理解,看下面一张图应该就能明白:

汉诺 塔 python 汉诺塔python心得体会_python


以上图的三层汉诺塔为例,开始柱指的是开始状态时存放所有盘子的柱子,中转柱指的是中间状态时暂时存放n-1个(三层就是3-1个)盘子的柱子,目标柱指的是盘子最终要移动到的柱子。这里需要注意,开始柱,中转柱,目标柱并不是一成不变的,而是会根据层次的不同而改变。(如果这里暂时不能理解的话,先读下去,再回头你就能明白了)。

接着我们分情况讨论一下盘子的移动:

  • 情况一:当盘子只有1个(调用 move(1,A,B,C))
    当盘子只有一个的时候,只要直接将盘子从开始柱移动到目标柱就可以了,并没有中间状态(即不用借助中转柱),在move方法中可以用一句话表示该移动动作 print(‘A—>C’);
  • 情况二:当盘子有2个(调用 move(2,A,B,C))
    这个情况分三个步骤进行:
    step1. 把除了最大的盘子之外的盘子从A移到B
    A—>B (开始柱—>中转柱) 【相当于调用 move(1,A,C,B)】
    step2. 把最大的盘子从A移到C
    A—>C (开始柱—>目标柱) 【相当于调用 move(1,A,B,C)】
    step3. 把除了最大的盘子之外的盘子从B移到C
    B—>C (中转柱—>目标柱) 【相当于调用 move(1,B,A,C)】
  • 情况三:当盘子有3个(调用 move(3,A,B,C))
    这个情况同样分三步进行:
    step1. 把除了最大的盘子之外的盘子从A移到B(注意对于这个步骤来说此时A为开始柱,C为中转柱,B为目标柱,这样才能完成把最上面的2个盘子从A—>B的任务)
    A—>C (开始柱—>中转柱) 【相当于调用 move(1,A,B,C)】
    A—>B (开始柱—>目标柱) 【相当于调用 move(1,A,C,B)】
    C—>B (中转柱—>目标柱)【相当于调用 move(1,C,A,B)】
    step2. 把最大的盘子从A移到C(对于这个步骤来说此时A为开始柱,B为中转柱,C为目标柱,这样才能把最大的盘子从A—>C)
    A—>C (开始柱—>目标柱) 【相当于调用 move(1,A,B,C),即直接执行 print(‘A—>C’)】
    step3. 把除了最大的盘子之外的盘子从B移到C(注意对于这个步骤来说此时B为开始柱,A为中转柱,C为目标柱,这样才能完成把处于step2中的中转柱的2个盘子从B—>C的任务)
    B —> A (开始柱—>中转柱) 【相当于调用 move(1,B,C,A)】
    B —> C (开始柱—>目标柱) 【相当于调用 move(1,B,A,C)】
    A —> C (中转柱—>目标柱) 【相当于调用 move(1,A,B,C)】

情况三的描述可能一下子不是那么好理解,但是情况三的step1和step3的形式和整个情况二的形式很像,而且要注意到分析的层次不相同时,开始柱,中转柱,目标柱是不一样的。对于step1来说中转柱是C,对于step3来说中转柱是A,对于整个情况三来说中转柱是B。

  • 情况二调用的函数是 move(2,A,B,C),其等价于这三步
    A—>B
    A—>C
    B—>C
  • 情况三的step1是
    A—>C
    A—>B
    C—>B
    这三步,跟情况二的形式是一样的,根据前面情况二的转化,那这三步就可以转化成函数 move(2,A,C,B)
    情况三的step3同理,做转化就成了函数 move(2,B,A,C)
    而情况三的step2可以直接用一句 print(‘A—>C’) 来代替 move(1,A,B,C)
    所以整个情况三就可以这样来表示:
    move(2,A,C,B) //step1. 把除了最大的盘子之外的盘子从A移到B
    print(‘A—>C’) //step2. 把最大的盘子从A移到C
    move(2,B,A,C) //step3. 把除了最大的盘子之外的盘子从B移到C
  • 来到这里应该就可以发现一点端倪了,情况四(4层)调用的函数是 move(4,A,B,C),其用伪代码表示就是
    move(3,A,C,B) //step1. 把除了最大的盘子之外的盘子从A移到B
    print(‘A—>C’) //step2. 把最大的盘子从A移到C
    move(3,B,A,C) //step3. 把除了最大的盘子之外的盘子从B移到C
  • 那其实可以总结出:对于n(n>1)层汉诺塔,调用函数 move(n,A,B,C)来递归解决该问题,该函数执行的是
    move(n-1,A,C,B) //step1. 把除了最大的盘子之外的盘子从A移到B
    print(‘A—>C’) //step2. 把最大的盘子从A移到C
    move(n-1,B,A,C) //step3. 把除了最大的盘子之外的盘子从B移到C
def move(n, A='A', B='B', C='C'):
    if n == 1:  # 1个盘子,直接打印出移动方位;递归的终止条件
        print(A, '--->', C)
    else:  # n > 1时,用抽象出的3步来移动
        move(n - 1, A, C, B)  # step1.  把除了最大的盘子之外的盘子从A移到B
        print(A, '--->', C)  # step2.  把最大的盘子从A移到C
        move(n - 1, B, A, C)  # step3.  把除了最大的盘子之外的盘子从B移到C