汉诺塔的移动规则
- 有三根柱子A,B,C(分别作为开始柱,中转柱,目标柱)。
- 开始柱上有若干盘子,每次移动一块盘子,小盘只能叠在大盘的上面
- 实现将所有盘子从开始柱全部移动到目标柱上。
汉诺塔的抽象理解
- 当只有一个盘子时,即移动一步,开始柱->目标柱
- 当有两个盘子时,需要使用中转柱,先将小盘从开始柱->中转柱,再将大盘从开始柱->目标柱,最后将小盘从中转柱->目标柱
- 以此类推,当有n个盘子时可以将(n-1)个小盘视为一个整体,此时就简化成了只有两个盘子时的情况。即先将(n-1)个小盘从开始柱->中转柱,再将大盘从开始柱->目标柱,最后将(n-1)个小盘从中转柱->目标柱。盘子数目一次递减调用移动两个盘子时的情况,可用递归方法。
汉诺塔的步骤规律分析
要用程序来解决这个问题,我们先定义一个移动函数:move(移动数,开始柱,中转柱,目标柱),例如 move(2,A,B,C) 表示将2个盘子从A柱(开始柱)借助B柱(中转柱)移动到C柱(目标柱)。关于开始柱,中转柱,目标柱这三个概念可以用移动过程中的某个状态来理解,看下面一张图应该就能明白:
以上图的三层汉诺塔为例,开始柱指的是开始状态时存放所有盘子的柱子,中转柱指的是中间状态时暂时存放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