两种方法解决hannoi塔问题(理解递归思想与非递归巧妙方法)

我们先把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)

默默地说:其实是自己不想用入栈出栈方法写非递归。。。总体思路上,个人觉得非递归思路好想,递归代码精简,但是开始愣是理解了半天,所以说总结一条 :代码能看懂>代码精简

 

 

 

 

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值