汉诺塔
汉诺塔问题是一个经典的递归算法问题,其核心思想是将一个由大到小叠放的圆盘从一根柱子移动到另一根柱子,过程中遵循以下规则:
- 每次只能移动一个圆盘;
- 圆盘只能在三根柱子之间移动;
- 小圆盘必须始终放在大圆盘之上。
定义数据结构
为了可视化显示移动过程,可以增加一个初始化方法,存储3根柱子对应有哪些盘子
python
def init(num: int):
column_dict = {}
column_dict['A'] = list(range(num, 0, -1))
column_dict['B'] = []
column_dict['C'] = []
print(0, column_dict)
return column_dict
初始化方法定义了一个字典,该字典有 A、B、C 三根柱子,A柱存储了一个列表的数据,如果输入为3 那么A 柱存储的盘子编号是 [3,2,1], 调用init(3)方法
输出结果如下
bash
>>> from tower_of_hanoi import init
>>> init(3)
0 {'A': [3, 2, 1], 'B': [], 'C': []}
{'A': [3, 2, 1], 'B': [], 'C': []}

列表 [3, 2, 1] 对应上图的盘子,数字越大,代表的盘子越大
移动1个盘子的方法
定义一个将一个盘子从源柱子 source 移动到 target 的方法,使用 step 列表记录当前移动了几次
python
def move_one_step(column_dict, source: str, target: str, step: list):
plate = column_dict[source].pop()
column_dict[target].append(plate)
step[0] = step[0] + 1
print(step[0], column_dict)
def move_one_plate(column_dict, source: str, target: str, mid: str, step: list):
move_one_step(column_dict, source, target, step)
运行 move_one_plate方法,查看终端的输出
python
if __name__ == '__main__':
step_list = [0]
column_dict = init(1)
move_one_plate(column_dict, 'A', 'C', 'B', step_list)
bash
0 {'A': [1], 'B': [], 'C': []}
1 {'A': [], 'B': [], 'C': [1]}
移动2个盘子的方法
python
def move_two_plate(column_dict, source: str, target: str, mid: str, step: list):
move_one_step(column_dict, source, mid, step)
move_one_step(column_dict, source, target, step)
move_one_step(column_dict, mid, target, step)
运行移动2个盘子的方法,得到如下输出
python
if __name__ == '__main__':
step_list = [0]
column_dict = init(1)
move_one_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
step_list = [0]
column_dict = init(2)
move_two_plate(column_dict, 'A', 'C', 'B', step_list)
bash
0 {'A': [1], 'B': [], 'C': []}
1 {'A': [], 'B': [], 'C': [1]}
-------------------------------------------
0 {'A': [2, 1], 'B': [], 'C': []}
1 {'A': [2], 'B': [1], 'C': []}
2 {'A': [], 'B': [1], 'C': [2]}
3 {'A': [], 'B': [], 'C': [2, 1]}
移动3个盘子的方法
python
def move_three_plate(column_dict, source: str, target: str, mid: str, step: list):
move_one_step(column_dict, source, target, step)
move_one_step(column_dict, source, mid, step)
move_one_step(column_dict, target, mid, step)
move_one_step(column_dict, source, target, step)
move_one_step(column_dict, mid, source, step)
move_one_step(column_dict, mid, target, step)
move_one_step(column_dict, source, target, step)
运行移动3个盘子的方法,得到如下输出
python
if __name__ == '__main__':
step_list = [0]
column_dict = init(1)
move_one_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
step_list = [0]
column_dict = init(2)
move_two_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
column_dict = init(3)
step_list = [0]
move_three_plate(column_dict, 'A', 'C', 'B', step_list)
bash
0 {'A': [1], 'B': [], 'C': []}
1 {'A': [], 'B': [], 'C': [1]}
-------------------------------------------
0 {'A': [2, 1], 'B': [], 'C': []}
1 {'A': [2], 'B': [1], 'C': []}
2 {'A': [], 'B': [1], 'C': [2]}
3 {'A': [], 'B': [], 'C': [2, 1]}
-------------------------------------------
0 {'A': [3, 2, 1], 'B': [], 'C': []}
1 {'A': [3, 2], 'B': [], 'C': [1]}
2 {'A': [3], 'B': [2], 'C': [1]}
3 {'A': [3], 'B': [2, 1], 'C': []}
4 {'A': [], 'B': [2, 1], 'C': [3]}
5 {'A': [1], 'B': [2], 'C': [3]}
7 {'A': [], 'B': [], 'C': [3, 2, 1]}
从第2条分割线开始看,初始化A柱有3个盘子,我们想要把他们都移动到C柱去,那么首先需要把[2,1] 盘子从A柱移动到B柱,从第一条分割线之后看,把2个盘子[2,1]从A柱移动到C柱的方法已经有了,我们可以直接调用之前移动2个盘子的方法,该变一下参数位置即可,新增以下方法 move_three_plate2
python
def move_three_plate2(column_dict, source: str, target: str, mid: str, step: list):
move_two_plate(column_dict, source, mid, target, step)
在主函数中运行一下
python
if __name__ == '__main__':
step_list = [0]
column_dict = init(1)
move_one_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
step_list = [0]
column_dict = init(2)
move_two_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
column_dict = init(3)
step_list = [0]
move_three_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
column_dict = init(3)
step_list = [0]
move_three_plate2(column_dict, 'A', 'C', 'B', step_list)
bash
0 {'A': [1], 'B': [], 'C': []}
1 {'A': [], 'B': [], 'C': [1]}
-------------------------------------------
0 {'A': [2, 1], 'B': [], 'C': []}
1 {'A': [2], 'B': [1], 'C': []}
2 {'A': [], 'B': [1], 'C': [2]}
3 {'A': [], 'B': [], 'C': [2, 1]}
-------------------------------------------
0 {'A': [3, 2, 1], 'B': [], 'C': []}
1 {'A': [3, 2], 'B': [], 'C': [1]}
2 {'A': [3], 'B': [2], 'C': [1]}
3 {'A': [3], 'B': [2, 1], 'C': []}
4 {'A': [], 'B': [2, 1], 'C': [3]}
5 {'A': [1], 'B': [2], 'C': [3]}
6 {'A': [1], 'B': [], 'C': [3, 2]}
7 {'A': [], 'B': [], 'C': [3, 2, 1]}
-------------------------------------------
0 {'A': [3, 2, 1], 'B': [], 'C': []}
1 {'A': [3, 2], 'B': [], 'C': [1]}
2 {'A': [3], 'B': [2], 'C': [1]}
3 {'A': [3], 'B': [2, 1], 'C': []}
调用 move_two_plate(column_dict, 'A', 'B', 'C', step) 完成了移动3个盘子的前3步,此时可以将A柱的最大的盘子移动到C柱啦,而且还复用了之前的代码。修改move_three_plate2 方法,移动3号盘从A到C
python
def move_three_plate2(column_dict, source: str, target: str, mid: str, step: list):
move_two_plate(column_dict, source, mid, target, step)
move_one_step(column_dict, source, target, step)
运行代码,得到以下输出
bash
0 {'A': [1], 'B': [], 'C': []}
1 {'A': [], 'B': [], 'C': [1]}
-------------------------------------------
0 {'A': [2, 1], 'B': [], 'C': []}
1 {'A': [2], 'B': [1], 'C': []}
2 {'A': [], 'B': [1], 'C': [2]}
3 {'A': [], 'B': [], 'C': [2, 1]}
-------------------------------------------
0 {'A': [3, 2, 1], 'B': [], 'C': []}
1 {'A': [3, 2], 'B': [], 'C': [1]}
2 {'A': [3], 'B': [2], 'C': [1]}
3 {'A': [3], 'B': [2, 1], 'C': []}
4 {'A': [], 'B': [2, 1], 'C': [3]}
5 {'A': [1], 'B': [2], 'C': [3]}
6 {'A': [1], 'B': [], 'C': [3, 2]}
7 {'A': [], 'B': [], 'C': [3, 2, 1]}
-------------------------------------------
0 {'A': [3, 2, 1], 'B': [], 'C': []}
1 {'A': [3, 2], 'B': [], 'C': [1]}
2 {'A': [3], 'B': [2], 'C': [1]}
3 {'A': [3], 'B': [2, 1], 'C': []}
4 {'A': [], 'B': [2, 1], 'C': [3]}
观察当前的数据,可以很容易发现,我们还需要将B柱的2个盘子[2,1]移动到C柱,修改 move_three_plate2,再次调用 move_two_plate 方法,将B柱的2个盘子[2,1]移动到C柱
python
def move_three_plate2(column_dict, source: str, target: str, mid: str, step: list):
move_two_plate(column_dict, source, mid, target, step)
move_one_step(column_dict, source, target, step)
move_two_plate(column_dict, mid, target, source, step)
运行代码,得到以下输出
bash
0 {'A': [1], 'B': [], 'C': []}
1 {'A': [], 'B': [], 'C': [1]}
-------------------------------------------
0 {'A': [2, 1], 'B': [], 'C': []}
1 {'A': [2], 'B': [1], 'C': []}
2 {'A': [], 'B': [1], 'C': [2]}
3 {'A': [], 'B': [], 'C': [2, 1]}
-------------------------------------------
0 {'A': [3, 2, 1], 'B': [], 'C': []}
1 {'A': [3, 2], 'B': [], 'C': [1]}
2 {'A': [3], 'B': [2], 'C': [1]}
3 {'A': [3], 'B': [2, 1], 'C': []}
4 {'A': [], 'B': [2, 1], 'C': [3]}
5 {'A': [1], 'B': [2], 'C': [3]}
6 {'A': [1], 'B': [], 'C': [3, 2]}
7 {'A': [], 'B': [], 'C': [3, 2, 1]}
-------------------------------------------
0 {'A': [3, 2, 1], 'B': [], 'C': []}
1 {'A': [3, 2], 'B': [], 'C': [1]}
2 {'A': [3], 'B': [2], 'C': [1]}
3 {'A': [3], 'B': [2, 1], 'C': []}
4 {'A': [], 'B': [2, 1], 'C': [3]}
5 {'A': [1], 'B': [2], 'C': [3]}
6 {'A': [1], 'B': [], 'C': [3, 2]}
7 {'A': [], 'B': [], 'C': [3, 2, 1]}
从移动数据可以看出 move_three_plate2 实现了移动3个盘子的最优方案,和之前的方法 move_three_plate 移动盘子过程一致。对比 move_three_plate2、move_two_plate 我们发现参数都是相同的,只不过所移动盘子的数量不一样,可以尝试修改成递归的方法,复制 move_three_plate2 方法命名为move,
将方法里面的 move_three_plate 也修改成 move,得到如下新方法
python
def move(column_dict, source: str, target: str, mid: str, step: list):
move(column_dict, source, mid, target, step)
move_one_step(column_dict, source, target, step)
move(column_dict, mid, target, source, step)
在主函数中运行 move 方法
if __name__ == '__main__':
step_list = [0]
column_dict = init(1)
move_one_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
step_list = [0]
column_dict = init(2)
move_two_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
column_dict = init(3)
step_list = [0]
move_three_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
column_dict = init(3)
step_list = [0]
move_three_plate2(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
column_dict = init(3)
step_list = [0]
move(column_dict, 'A', 'C', 'B', step_list)
终端输出如下
bash
Exception has occurred: RecursionError
maximum recursion depth exceeded
方法一直在递归调用自己,没有递归的出口,达到方法递归调用栈的最大深度报错。因此,可以增加一个参数n,来记录当前需要移动盘子的个数,当n=1时候,只需要移动1最后一个盘子,完成之后,退出递归。修改之后的 move 方法如下:
python
def move(column_dict, n: int, source: str, target: str, mid: str, step: list):
if n == 1:
move_one_step(column_dict, source, target, step)
return
move(column_dict, n - 1, source, mid, target, step)
move_one_step(column_dict, source, target, step)
move(column_dict, n - 1, mid, target, source, step)
运行主函数,得到如下输出
python
if __name__ == '__main__':
step_list = [0]
column_dict = init(1)
move_one_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
step_list = [0]
column_dict = init(2)
move_two_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
column_dict = init(3)
step_list = [0]
move_three_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
column_dict = init(3)
step_list = [0]
move_three_plate2(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
column_dict = init(3)
step_list = [0]
move(column_dict, 3, 'A', 'C', 'B', step_list)
bash
0 {'A': [1], 'B': [], 'C': []}
1 {'A': [], 'B': [], 'C': [1]}
-------------------------------------------
0 {'A': [2, 1], 'B': [], 'C': []}
1 {'A': [2], 'B': [1], 'C': []}
2 {'A': [], 'B': [1], 'C': [2]}
3 {'A': [], 'B': [], 'C': [2, 1]}
-------------------------------------------
0 {'A': [3, 2, 1], 'B': [], 'C': []}
1 {'A': [3, 2], 'B': [], 'C': [1]}
2 {'A': [3], 'B': [2], 'C': [1]}
3 {'A': [3], 'B': [2, 1], 'C': []}
4 {'A': [], 'B': [2, 1], 'C': [3]}
5 {'A': [1], 'B': [2], 'C': [3]}
6 {'A': [1], 'B': [], 'C': [3, 2]}
7 {'A': [], 'B': [], 'C': [3, 2, 1]}
-------------------------------------------
0 {'A': [3, 2, 1], 'B': [], 'C': []}
1 {'A': [3, 2], 'B': [], 'C': [1]}
2 {'A': [3], 'B': [2], 'C': [1]}
3 {'A': [3], 'B': [2, 1], 'C': []}
4 {'A': [], 'B': [2, 1], 'C': [3]}
5 {'A': [1], 'B': [2], 'C': [3]}
6 {'A': [1], 'B': [], 'C': [3, 2]}
7 {'A': [], 'B': [], 'C': [3, 2, 1]}
-------------------------------------------
0 {'A': [3, 2, 1], 'B': [], 'C': []}
1 {'A': [3, 2], 'B': [], 'C': [1]}
2 {'A': [3], 'B': [2], 'C': [1]}
3 {'A': [3], 'B': [2, 1], 'C': []}
4 {'A': [], 'B': [2, 1], 'C': [3]}
5 {'A': [1], 'B': [2], 'C': [3]}
6 {'A': [1], 'B': [], 'C': [3, 2]}
7 {'A': [], 'B': [], 'C': [3, 2, 1]}
最后一条分割线之后的数据就是调用 move 方法移动的输出,完成了3个盘子的移动过程。至此,😁递归的推导过程也完成了
完整的代码
python
def init(num: int):
column_dict = {}
column_dict['A'] = list(range(num, 0, -1))
column_dict['B'] = []
column_dict['C'] = []
print(0, column_dict)
return column_dict
def move_one_step(column_dict, source: str, target: str, step: list):
plate = column_dict[source].pop()
column_dict[target].append(plate)
step[0] = step[0] + 1
print(step[0], column_dict)
def move_one_plate(column_dict, source: str, target: str, mid: str, step: list):
move_one_step(column_dict, source, target, step)
def move_two_plate(column_dict, source: str, target: str, mid: str, step: list):
move_one_step(column_dict, source, mid, step)
move_one_step(column_dict, source, target, step)
move_one_step(column_dict, mid, target, step)
def move_three_plate(column_dict, source: str, target: str, mid: str, step: list):
move_one_step(column_dict, source, target, step)
move_one_step(column_dict, source, mid, step)
move_one_step(column_dict, target, mid, step)
move_one_step(column_dict, source, target, step)
move_one_step(column_dict, mid, source, step)
move_one_step(column_dict, mid, target, step)
move_one_step(column_dict, source, target, step)
def move_three_plate2(column_dict, source: str, target: str, mid: str, step: list):
move_two_plate(column_dict, source, mid, target, step)
move_one_step(column_dict, source, target, step)
move_two_plate(column_dict, mid, target, source, step)
def move(column_dict, n: int, source: str, target: str, mid: str, step: list):
if n == 1:
move_one_step(column_dict, source, target, step)
return
move(column_dict, n - 1, source, mid, target, step)
move_one_step(column_dict, source, target, step)
move(column_dict, n - 1, mid, target, source, step)
if __name__ == '__main__':
step_list = [0]
column_dict = init(1)
move_one_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
step_list = [0]
column_dict = init(2)
move_two_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
column_dict = init(3)
step_list = [0]
move_three_plate(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
column_dict = init(3)
step_list = [0]
move_three_plate2(column_dict, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
column_dict = init(3)
step_list = [0]
move(column_dict, 3, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
for n in range(4, 10):
column_dict = init(n)
step_list = [0]
move(column_dict, n, 'A', 'C', 'B', step_list)
print('-------------------------------------------')
输出太长了,这里就不展示了