汉诺塔问题

2019-08-10  本文已影响0人  愤怒的熊猫V

一个n层的汉诺塔,从A移动到C

由于汉诺塔问题本身的限制,我们最先能思考到的点是第n层最后肯定是要放在C的最下面的

有了这个思考后,我们又想,要想使汉诺塔移动次数最小

—— —— —— ——                          ???                                        空         

         A                                                    B                                            C

那么我们就要满足可以A中只剩下n这一张圆盘了,而C中是空的,那么我们就可以直接将A中这一张圆盘直接放入C中

OK,A.C的结构确定了,那B的结构是什么呢?

很显然,由于汉诺塔的特性,所以B中此时是n-1层的汉诺塔

我们再将B中的n-1层汉诺塔移动到C中即可,这个操作完全等同于将n-1层汉诺塔从A中移动至C,且B,C为空这一初始条件

因为B中的n-1层圆盘本身满足汉诺塔结构,且第n-1层是小于C中的第n层的也就是可以将C也看成是空的,所以我们可以乱放,怪放,无限放,终极放,究极皮皮放!

OK,我们还要考虑的是从A中移动n-1层到B,这个问题和从A中移动n-1层到C完全一样!!!

所以抛开第n个圆盘从A移动到C这一个操作外

我们还有从A移动整个n-1层汉诺塔到B,然后从B将整个n-1层汉诺塔移动到C的这两步操作!

OK,无论你叫他递归问题也好,DP问题也好,我觉得更像是递归问题,但我还是写成DP的样子把

DP[n] = 2*DP[n-1]+1

写成递归就好

def hanoi(n):

    if n == 1:

        return 1

    result = 0

    result += hanoi(n-1)

    return result


如果告诉你汉诺塔盘随机地分布在三个柱子上(前提条件是满足每个柱子上依然满足汉诺塔结构),然后问当前状态是不是汉诺塔从A移动到C的必经状态,如果不是返回-1,如果是,返回是当前的第几步

可以这样思考,假设第二个柱子上出现了n号盘子,我们可以肯定的是它不是必经状态因为第n号盘子肯定是直接从A移动到C的

OK有了这个思路就好办了

我们记主柱子为main,辅助柱子为sup,目的柱子为tar

这样记录是有目的的

假设三个汉诺塔是这样的结构

A:main:[X    X]

B:sup:[X    X]

C:tar:[X    X]

假设总共有6号盘子,由上面的结论,从A移动6个盘子到C中总共需要2^6-1 = 63种

我们检查最大的数6在哪个位置

我们知道的是从A将第6号盘子移动到C中是第(63+1)//2   +1  =   32步,

如果6处在sup中,肯定返回-1,

不是的话,我们再看6处在哪个位置

假设6处在tar中,我们可以认为,它可能是将n-1层汉诺塔从sup(B)移动到tar(C)的一个中间过程

那么这个状态就有可能是第34步到第63步中的某一步

记住我们现在的目标是从B中将5层汉诺塔移动到C中,那么此时就与第6个汉诺塔无关

我们将tar中队列最前面的数字弹出tar.pop(0)

且现在的主柱子是B,目标柱子是C,辅助柱子是A,即

A:sup[X    X]

B:main[X    X]

C:tar[X]

同样的,我们看5在哪,如果5在A中,即辅助柱中,肯定没戏,如果在主柱子中,那么肯定在第32到(63-32+1)//2 +1步中

如此循环

因此我们的程序应该是这样的

因为从A移动n个到C中,所以我们记A为主,B为辅,C为目标,且把所有的盘子按1到n进行标号,

将状态放入A.B.C三个列表中

main = [X,X,X,X,X]

sup   = [X,X,X,X,X,X,X]

tar     = [X,X,X,X]

两个指针记录区间

left = 1,right = 2^n-1

当left和right重合时,就是找到的次数

while    left!  !=    right:

        #如果n在辅助队列中直接返回不在必经之路上

        if    n    in    sup:

                return -1

        #如果n在主队列中,将右区间缩减到原区间的终点的左侧

        #且    将目标队列和辅助队列交换位置

        #且    将n从main中弹出

        elif    n    in    main:

                right    =    (left+right)//2-1

                main.pop(0)

                sup,tar    =    tar,sup

         elif    n    in    tar:

                    left    =    (left+right)//2+1

                    tar.pop(0)

                    sup,main    =    main,sup

reuturn left


考虑了一会儿,发现上面一个漏洞

我们的left和right都是指移动了多少步

而最原始的状态应该表示移动了第0步

所以left的初始值应为0

上一篇下一篇

猜你喜欢

热点阅读