我们先把hannoi塔的问题描述一遍:现在有三根柱子A,B,C,A柱子上有N个盘子(N个盘子有大有小,并且已经按照上小下大的顺序摆好)现在要求要把A上的所有盘子移到C上,每次只能移动一个盘子,中途可以经过B柱子,在每次移动后三根柱子上必须保证盘子是上小下大或者没有盘子。这个问题我们需要关注的方向是最少需要移动几次和移动的路径。
算法
首先假定A上放了3个盘子可以定为小,中,大。第一步有两种选择,A-->C或者A-->B,经过画图验证A-->C最后移动次数最少(读者自己画图验证,这一个结论很关键,可以理解为只有一个盘子时候的最优路径,以后的每一次移动都必须是最优路径)
其实可以把这N个盘子分成两部分:最底端一层和上面的N-1个盘子,多次画图总结规律,总体需要三步:
第一步:我们要将A上前N-1个盘子移动到B上;
第二步:我们将A柱子上第N个也就是最后一个盘子移到C
第三步:我们再将B上前N-1个盘子移到C
那么第一步也要进一步细化:
第一步:我们要将A上前N-2个盘子移到B上
第二步:我们要将第N-1个盘子从A移到C
第三步:我们把B上N-2个盘子移到C上
从上面步骤我们可以发现,这似乎是一个无穷无尽的过程,但是递归的优势在于他会逐渐递归至N=1,这个繁琐的过程就由计算机来完成。
废话不多说,上代码:
# 利用递归函数移动汉诺塔:
'''n代表a上盘子总数,a,b,c代表三个柱子'''
def move(n, a, b, c):
# 首项:N = 1情况
if n == 1:
print('move', a, '-->', c)
else:
# N>=2时候
# 把前n-1个盘子从a借助c移到b
move(n-1, a, c, b)
#把最后一个盘子从a借助b移到c
move(1, a, b, c)
#把前n-1个盘子从b借助a移到c
move(n-1, b, a, c)
#最后发现这个递归和二叉树中序递归遍历基本一致
move(4, 'A', 'B', 'C')
显而易见,这个递归的算法复杂度很大,因为要移动2^n-1次盘子,那么我们有一个大胆的猜想,这个和二进制数有没有什么联系。在网上看到某大牛用二进制思维来理解:
假设三层高的汉诺塔,对应盘子编号‘000’,‘001’,‘010’ 对应0,1,2号盘子 往右移一位,就进位1,移两位就进位2,然后发现数字变为‘111’时候三个盘子都会在C上,(如果有四个盘子,会发现所有盘子会移到中盘)
总结规律:非递归需要分A中原始盘子为奇数 偶数情况,奇数,盘子顺序:A B C;偶数是:A C B
def hanoi(n):
tower_num= [0] * n # 用列表开辟 n 个空间,用于存放 n 个盘子各自的编号,编号对应塔号
# 盘子移动,编号对应更改
if n % 2 == 0: # 盘子数量不同,塔的排序不同,主要分奇数偶数两种情况
tower_name = ['A', 'B', 'C'] # 若 n 为偶数,最终所有盘子恰好能移到右塔
else:
tower_name = ['A', 'C', 'B'] # 若 n 为奇数,最终所有盘子会移到中塔
for step in range(1, 2**n): # n 个盘子最少需要移动 2^n - 1 次
bin_step = bin(step) # 求得 step 的二进制数值
gold_num = len(bin_step) - bin_step.rfind('1') - 1
# 计算 step 末尾 0 的个数,得到盘子编号;上面说的“规律一”
# 如 step = 0b0001,则 step 末尾 0 的个数为 0,表示此刻应移动 0 号金片
# 如 step = 0b0100,则 step 末尾 0 的个数为 2,表示此刻应移动 2 号金片,依此类推
# rfind 是从 0 开始计数,所以再减个 1
print(f"第 {step:2} 步:移动 {str(gold_num)} 号金片,从 {tower_name[tower_num[gold_num]]} 塔到", end=' ') # 移出盘子的塔
if gold_num % 2 == 0: # 若 num 为 偶数,则右移
tower_num[gold_num] = (tower_num[gold_num] + 1) % 3
# 若从 C 塔右移,则又回到了 A 塔
else: # 若 num 为奇数,则左移
tower_num[gold_num] = (tower_num[gold_num] + 2) % 3
# 若从 A 塔左移,则又去到了 C 塔
print(tower_name[tower_num[gold_num]], '塔') # 移入盘子的塔
n = int(input('请输入汉诺塔层数:'))
hanoi(n)
默默地说:其实是自己不想用入栈出栈方法写非递归。。。总体思路上,个人觉得非递归思路好想,递归代码精简,但是开始愣是理解了半天,所以说总结一条 :代码能看懂>代码精简