对汉诺塔问题的解决算法

一.问题描述:

相传在古印度圣庙中,有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。

二.关于递归问题。

迭代的是人,递归的是神 —— L.Peter Deutsch

在初识递归的时候,我们经常会陷入不停的回溯验证之中,因为回溯验证就像反过来思考迭代,这是我们习惯的思考方式,但是对于递归,我们并不需要这样验证。比如阶乘计算:

用回溯的方式思考:比如 n = 4 那么4!等于 4 * 3! 而 3! 等于 3 * 2!,2!等于 2 * 1!, 而1!等于1 所以4!= 4 * 3 * 2 * 1 这个结果正好是等于阶乘4的迭代定义

但是这种方法对于简单的还好 ,如果是复杂就会被绕晕, 无益于理解

Paul Graham提到一种方法,如下:

  1. 当n=0,1的时候,结果正确(你必须要示范如何解决问题的一般情况, 通过将问题切分成有限小并更小的子问题)

  2. 假设函数对于n是正确的,函数对n+1的结果也是正确的

    如果这两点成立,我们知道这个函数对于所有可能的n都是正确的

    (注意:1.你必须亲自示范如何通过有限步骤来解决最小的问题,一般来说,最小的问题等价于n等于1时的情况(基本用例);2.对于有限个步骤的解决方法,每当步骤多一个层次的时候解决方法同样适用。当这两个问题被解决的时候,这个递归问题也就被解决了)

这种方法很像 数学归纳法, 也是递归正确的思考方式, 事实上, 阶乘的递归表达方式就是1!=1,n!=(n-1)!×n. 当程序实现符合算法描述的时候, 程序自然对了, 假如还不对, 那是算法本身错了…… 相对来说, n,n+1的情况为通用情况, 虽然比较复杂, 但是还能理解, 最重要的, 也是最容易被新手忽略的问题在于第1点, 也就是基本用例要对. 比如, 对于上述的阶乘问题我们的程序实现如下:

def fact(n):
    if n==1:
        return 1
    return n*fact(n-1)

print(fact(n))

当上述程序中的n=1的算法出错的时候,整个递归算法也会出错。万丈高楼平地起,因此次对于递归问题,编写好基本用例算法是关键。

三.汉诺塔解决算法

言归正传,在理解了递归后我们来看汉诺塔解决算法。重新回看一下题目,结合递归方法我们可以先想一下如何编写算法解决该问题。当n等于1,2,3的时候我们很轻松的就能想出最优解决方法,但当n大到一定范围的时候的时候,任何人都不可能很清楚的写出每一个步骤。因此我们先从基本用例分析:

1.当n=1时:解决步骤是A杆->C杆。

2.当n=2时:解决步骤是A杆->B杆,A杆->C杆,B杆->C杆.

3.当n=3是:解决步骤是A杆->C杆,A杆->B杆,C杆->B杆,A杆->C杆,B杆->A杆,B杆->C杆,A杆->C杆

...........

是不是感觉已经被绕晕了(ps:我也有点跟不上节奏了)。因此我们换个角度思考问题,通过有限个步骤,直觉告诉我们当解决汉诺塔问题时,要使n个铜板到某个杆上面,最先解决的应该是使n-1个铜板装移到中介杆上(1)(ps:记住此原理,下面会用到),此处我觉得使用步骤来讲会清楚一点,如下:

1.n个铜板要按规定从A杆移动到C杆,首先考虑n-1个铜板从A杆移动到B杆(1).

2.然后将第n个铜板移动到C杆。

3.对于n-1个铜板从A杆到B杆,首先要解决n-2个铜板从A杆到C杆(1),

4.对于n-2个铜板从A杆到C杆,我们可以这样简化问题,我们考虑忽略掉第n-1,n-2个铜板,,铜板基数变为n-2,因此这又是关于n-2个铜板的递归问题,初始杆变为B杆,因此过程应该是n-3个铜板从B杆移动到C杆(实际上,你会发现每一次对于n-2的递归,初始杆会在A,B间循环)。.

5.一直重复步骤1,2,3直到n=1.

递归问题有点类似于循环论证问题,使用计算机程序解决算法的好处就是通过编写简短的循环算法可以解决整个递归问题。

算法程序实现如下:

def move(n, a, b, c):#定义递归函数,n为铜板基数,a,b,c按顺序分别为A,B,C杆(注意,此处是按顺序写的形参,不是名字)
    if n == 1:#n=1时跳出递归函数
        print('move', a, '-->', c)
    else:
        move(n-1, a, c, b)#上述提到的步骤一.
        move(1, a, b, c)#步骤二
        move(n-1, b, a, c)#步骤三

当不满足n=1时,函数就会执行步骤4,5直到n=1。

下面我们来验证算法:

move(10, 'A', 'B', 'C')

结果:

move A --> B
move A --> C
move B --> C
move A --> B
move C --> A
move C --> B
move A --> B
move A --> C
move B --> C
move B --> A
move C --> A
move B --> C
move A --> B
move A --> C
move B --> C
move A --> B
move C --> A
move C --> B
move A --> B
move C --> A
move B --> C
move B --> A
move C --> A
move C --> B
move A --> B
move A --> C
move B --> C
move A --> B
move C --> A
move C --> B
move A --> B
move A --> C
move B --> C
move B --> A
move C --> A
move B --> C
move A --> B
move A --> C
move B --> C
move B --> A
move C --> A
move C --> B
move A --> B
move C --> A
move B --> C
move B --> A
move C --> A
move B --> C
move A --> B
move A --> C
move B --> C
move A --> B
move C --> A
move C --> B
move A --> B
move A --> C
move B --> C
move B --> A
move C --> A
move B --> C
move A --> B
move A --> C
move B --> C
第一篇博客到这里就结束了,如果各位大佬觉得我的文章有不对的地方可以留言,我会虚心接受并改正.谢谢观看

  • 3
    点赞
  • 3
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

幻想流浪人

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值