【递归算法】汉诺塔

汉诺塔

汉诺塔问题是一个经典的递归算法问题,其核心思想是将一个由大到小叠放的圆盘从一根柱子移动到另一根柱子,过程中遵循以下规则:

  1. 每次只能移动一个圆盘;
  2. 圆盘只能在三根柱子之间移动;
  3. 小圆盘必须始终放在大圆盘之上。

定义数据结构

为了可视化显示移动过程,可以增加一个初始化方法,存储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_plate2move_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('-------------------------------------------')

输出太长了,这里就不展示了

相关推荐
2501_941652772 小时前
基于YOLO12-A2C2f-FRFN的电缆连接器类型识别与定位
python
喵手2 小时前
Python爬虫实战:采集双色球(SSQ)历史开奖数据(期号、红球、蓝球、开奖日期)等信息,并进行结构化CSV存储(Requests + Pandas)!
爬虫·python·爬虫实战·零基础python爬虫教学·双色球历史开奖数据·期号红球篮球开奖日期等·结构化csv存储
2401_838472512 小时前
C++中的装饰器模式实战
开发语言·c++·算法
白中白121382 小时前
算法题-06
算法
氵文大师2 小时前
PyTorch 性能分析实战:像手术刀一样精准控制 Nsys Timeline(附自定义颜色教程)
人工智能·pytorch·python
梦幻精灵_cq2 小时前
正文标题党——正文标题也需要精致
python
YMWM_2 小时前
python3中类的__call__()方法介绍
开发语言·python
爱学习的阿磊2 小时前
C++与Qt图形开发
开发语言·c++·算法
柠檬07112 小时前
cuda 安装记录
python