【递归算法】汉诺塔

汉诺塔

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

  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_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('-------------------------------------------')

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

相关推荐
启观川1 小时前
数据结构与算法-第 3 章 常用算法-贪心算法
数据结构·笔记·python·算法·贪心算法
南笙北梦1 小时前
本地开源大模型多轮对话显存泄漏排查与修复实录
python
对空六课1 小时前
指标口径生效时间的版本查询实现
大数据·服务器·前端·算法·数据分析
对空六课2 小时前
RFM分层边界变化的回溯计算
大数据·服务器·前端·数据库·人工智能·算法·数据分析
小羊没烦恼!2 小时前
如何在IIS6,7中部署ASP.NET网站?
数据库·后端·python·web安全·asp.net
一勺思维2 小时前
我为什么把 Agent 的核心逻辑从 LangChain 里搬出来手写
算法
碧海银沙音频科技研究院2 小时前
基于炬芯ATS308X的LE aduio LC3编解码算法功能实现
算法
小蒋观天下2 小时前
厂区人员徘徊游荡判别——基于时序轨迹速度的逗留行为分级算法落地
大数据·人工智能·深度学习·算法·机器学习·ai大模型
程序员清风2 小时前
堆与优先队列:从 Top K 到任务调度
java·数据结构·算法
shaibdoio2 小时前
瞬维AI技术拆解:AI获客智能体的双层漏斗算法原理
大数据·人工智能·算法