汉诺塔问题

汉诺塔问题

1.        经典问题描述:

经典汉诺塔问题是一个有简单递归解的非平凡问题的标准例子。在一个桩上放有n个不同大小的盘子,它们按递减的顺序自下而上。还有另外两个空的桩子,如图 1所示。这道难题的目标是移动多有的盘子,一次只能移一个,按照下面的方式从一个桩移到另一个桩的顶部。只有当盘子比另一个桩子上所有的盘子都小时,它才能移过去。换句话说,在整个期间,盘子大小自下而上递减的顺序必须保持。目标是,用尽可能少的步骤移动全部的盘子(算法引论-一种创造性方法,习题5.22-5.24)。

2.        递归分析

这里要解决的问题是要将所有n个盘子从一个桩子移到另外一个,并且要保证移动的步数最少。用递归方法进行分析,

归纳假设:已知如何用最小的方法将n-1个盘子移到另外一个桩子。

现在的问题变成,在归纳假设的基础上,即已知如何将n-1个盘子用最少的步数移到另外一个桩子上去,怎样用最小的步数移动第n个。由于移动的过程中必须遵循只有比桩子上所有的盘子都小时才能放入,假设目标是将盘子都移动到第三个桩子上,那么将n个盘子移到另一个桩子过程中必将会出现如图 2所示的一种状态,即首先需要将最小的n-1个盘子全部移到第二个桩子上,因为要将第n个盘子移到第三个桩子上时,它的上面不能有其它盘子,同时由于该盘子最大,目标第三个桩子不能有任何盘子。将该最大的盘子移动到第三个桩子上后,再将n-1个盘子从第二个桩子移到第三个桩子。该过程是移动过程中必须的(没有其它方法可以避开这样移动),如果移动n-1个盘子的移动步数可以保证最少,那么该过程可以保证移动n个盘子的移动步数最少。

设将n-1个盘子从一个桩子移动到另一个桩子的最少步数为,那么移动n个盘子的最少步数为将n-1个盘子移动到第二个桩子上的步数,加上移动最大盘子的一步,再加上将n-1个盘子从第二个桩子移动到第三个桩子上的步数,即 T(n)=2T(n-1)+1,可得出 T(n)=2^n-1.

将n个盘子按照汉诺塔的规则从一个桩子移动到另外一个桩子上的最小移动步数为T(n)=2^n-1,这是一个很大数字,当n=20时,该移动步数高达一百万次,这是由于移动过程中的严格的条件及只有一个临时储存空间造成的。

 

迭代算法描述1:假设当前要将n个盘子从桩子P1移动到桩子P3,P2是另外一个桩子,

re_MoveHT(P1,P3,n)//将n个盘子从桩子P1移动到桩子P3

{
 If(n==1)
  {
     If(P1最上面的盘子大于P3最上面的盘子)  error;
     Else 将P1最上面的盘子取出,放置在P3上面
  }
 Else
   {
    re_MoveHT(P1,P2,n-1);//将P1上面n-1个盘子移动到P2上面-------------(1)
    re_MoveHT(P1,P3,1);//将P1上的当前最大盘子移动到P3上--------------(2)
    re_MoveHT(P2,P3,n-1)//将n-1个盘子从P2移动到P3上面----------------(3)

   }
}

迭代算法通常用来对算法进行分析,但在实际过程使用迭代算法却比较少,一方面由于时间及储存空间(通常要用到栈)上的消耗,另一方面也不利于真正动手进行操作。下面将算法修改成非递归描述。

传统的方法就是利用堆栈来储存迭代的中间变量,这里我们用两个堆栈来储存中间信息,一个储存要移动的位置,另一个储存移动的目标位置。从前面的迭代分析中看出,如果要将第n个移动到P3桩子上,那么第n-1个需移动到P2桩子上,同理,第n-2个盘子需要移动到P3桩子上,以此类推…,这些过程我们用堆栈记录下来,当某一个盘子上面没有空盘子时,将按照该记录移动该盘子。

当按照堆栈的记录移动一个盘子后,接下来该干什么呢?前面的递归描述已经告诉我们了,例如如果将第n个盘子移动到P3桩子后,接下来要做的是将P2桩子上的n-1个盘子移动到P3桩子上,这又回到了前面的压栈过程。

这跟图或树的深度优先搜索非常相似,当沿着“路径”向前搜索时,我们使用堆栈记录走过的路,直至走到了路的尽头,对路尽头的节点进行访问,然后弹栈回溯,沿着分支继续搜寻路径…

非迭代描述方法2

MoveHT1(P1,P3,n)//将P1位置上的n个盘子移动到P3桩子上,P2为另外一个桩子

{
 T1=P1; T2=P2; T3=P3; N=n;//初始化
 While(没有完成全部搬移)
   {
     for(i=0;i<N;i++)
     {
     Stack1.push(T1);
     If(i为偶数) stack2.push(T3);//-----------(4)
     Else     stack2.push(T2);//-------------(5)
     }

   //将堆栈stack1最顶端对应的桩子上的盘子移动到stack2最顶端对应的桩子上去。

    Stack1.pop();
    Stack2.pop();
    //接下来要完成递归描述中式(3)的工作,及将P2中的n-1个盘子移动到P3上去
    If(T2上面没有盘子比刚移动的盘子,即现在T3上顶端的盘子小)
    {
     Temp=T3;
     T3为T1与T2中顶端盘子较大者对应的桩子或空的桩子;
     T1为T1与T2中顶端盘子较小者对应的桩子;
     T2=Temp;
    }
    Else
     {
      T3不变;
      T1与T2交换;
     }

     N等于T1桩子上小于T3桩子上顶端盘子的盘子总个数,如果T3为空,那么N为T1桩子上盘子总个数。

  }//while

}

这是采用堆栈将递归方法修改成非递归方法,事实上在汉诺塔的非迭代描述方法中,有更简单的描述方法。当我们知道要将P1上的n个盘子移动到P3桩上后,接下来直接移动的一个盘子是可以直接算出来的,即如程序段(4)与(5)所示,而移动一个盘子后,接下来要从T1桩子移动N个盘子到T3桩子,这些都可以简单的算出来的,然后继续循环。

非迭代描述方法3

MoveHT2(P1,P3,n)//将P1位置上的n个盘子移动到P3桩子上,P2为另外一个桩子
{
T1=P1; T2=P2; T3=P3; N=n;//初始化

 While(没有完成全部搬移)
   {

   根据T1,T2,T3,及N确定要移动的盘子
   将T1顶端的盘子移往T3;

   //接下来要完成递归描述中式(3)的工作,及将P2中的n-1个盘子移动到P3上去
   If(T2上面没有盘子比刚移动的盘子,即现在T3上顶端的盘子小)
   {
   Temp=T3;
   T3为T1与T2中顶端盘子较大者对应的桩子或空的桩子;
   T1为T1与T2中顶端盘子较小者对应的桩子;
   T2=Temp;
   }
  Else{
     T3不变;
     T1与T2交换;
    }

   N等于T1桩子上小于T3桩子上顶端盘子的盘子总个数,如果T3为空,那么N为T1桩子上盘子总个数。

  }//while
}

这两种非迭代算法都是根据迭代算法修改的,在整个过程中迭代方法为我们提供方向。

3.        汉诺塔的一般情况

前面是汉诺塔的经典状态,更一般的状态是初始时所有的盘在按照自下而上从大到小随机的洒落在三个桩子上,我们现在需要用最小的移动步数将所有的盘子,按照前面的规则移动到指定的一个桩子上去,如图 3所示。

归纳假设:已经知道如何将总盘子个数为n-1的一般情况下的汉诺塔移动到指定的桩子上去。

假设当前最大的盘在P1桩子上(其它情况类推),现在需要将所有的盘子移动到P3桩子上去,同迭代算法描述1一样,移动的过程中必将要出现如图 4所示的情况,需要先将n-1个盘子移动到P2,然后将第n个盘子从P1移动到P3,再将n-1个盘子从P2移动到P3桩子,这个过程是必须的。假设将一般情况下总个数为n-1的汉诺塔移到一个桩子上至少需要T(m1,m2,m3,p3),其中m1,m2,m3分别为三个桩子上对应的盘子个数,m1+m2+m3=n-1,P3为目标桩子,那么将n个一般情况下的汉诺塔移动到目标桩子上的最小移动步数为,

      T(m1,m2,m3,P3|m1+m2+m3=n)<=T(m4,m5,m6,P2|m1+m2+m3=n)+T(0,m1+m2+m3-1,0|m1+m2+m3=n)+1

而由前面所知,T(0,m1+m2+m3-1,0|m1+m2+m3=n)恰好是汉诺塔的经典情况,前面已经求取的其解为T(0,m1+m2+m3-1,0|m1+m2+m3=n)=2^n-1。这里的P1桩子上变成了m1-1对应图 3所示的情况,在实际情况中最大盘子的位置是随机的,也有可能最大盘子正好位于目标桩子上,所以这里只能给出移动步数的上限。

                                              

虽然这里给出的是移动步数的上限,其恰好的是汉诺塔的经典情况,即汉诺塔的经典情况是最糟糕的情况(其移动的步数最多),按照上述迭代方法得到的移动步数是最少的。

下面讨论一般情况下汉诺塔的一个有趣的情况,前面给出了经典汉诺塔的算法描述,对比可以发现,它们的迭代方法均分为三步,并且第二步与第三步是相同的,只是第一步不同而已。在非迭代描述方法3中,我们是从迭代方法的第二步开始考虑的,及如果已经移动了一步,下一步该怎么做。

这里讨论汉诺塔的一般情况下的非迭代方法,借鉴非迭代描述方法3中的思想,从第二部开始考虑,因为两种情况下的迭代描述方法第二部与第三步是一样的。在一般情况下,如果已经移动了一步,接下来采用同非迭代描述方法3一样的方法来确定下次要移动的盘子。现在的问题已经简化到需要确定一般情况下第一次要移动的盘子,这比经典情况下要复杂一些,不能简单的得到。这里我们利用一般情况下中递归的策略一直往前搜索,直至遇到可以直接移动的盘子就可以了。

非迭代描述方法4:

MoveHT_Nor(P3)//将一般情况下的汉诺塔的所有盘子移动到P3桩子上
{
 利用迭代搜索的方法获取第一次要移动的盘子,
 T1是盘子所在的桩子,T3是盘子要移往的桩子,T2是另外一个桩子
 While(没有完成全部搬移)
   {

   将T1顶端的盘子移往T3;
   //接下来要完成递归描述中式(3)的工作,及将P2中的n-1个盘子移动到P3上去
   If(T2上面没有盘子比刚移动的盘子,即现在T3上顶端的盘子小)
   {
    Temp=T3;
    T3为T1与T2中顶端盘子较大者对应的桩子或空的桩子;
    T1为T1与T2中顶端盘子较小者对应的桩子;
    T2=Temp;
   }
   Else{
     T3不变;
     T1与T2交换;
   }
 
   N等于T1桩子上小于T3桩子上顶端盘子的盘子总个数,如果T3为空,那么N为T1桩子上盘子总个数。

 }//while

 根据T1,T2,T3,及N确定要移动的盘子
}


综合汉诺塔的两种情况,我们只需要首先确定第一次要移动的盘子及如何移动,然后就可以比较简单的来完成该游戏了。第一步移动很重要,因为它决定了整个算法的运行方向,第一步错了,就不再能保证最少移动步数了。


 



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值