Xiangism

从一个无知角落里开始,蹒跚学步,一个未知到另一个未知,在跌跌撞撞中越走越快,越走越远,最后宇宙也为之开源。对于探索者来说,最后他们的思想总是变得和自己的足迹一样伟大。
  博客园  :: 首页  :: 联系 :: 管理

非递归汉斯塔

Posted on 2010-05-02 18:34  Xiangism  阅读(1602)  评论(6编辑  收藏  举报

最近在看了大学算法的教程后,深深地理解到了递归算法在某些情况下的局限性,于是就想到了两年前自己做的汉斯塔的项目中的递归算法。能否将其变成循环算法呢?

 

为了简化,我是在VC++的控制台下来完成这个算法。

为了让程序更加清晰,定义了两个类Peg(柱子类),Disk(盘子类),当然内容就简化多了。类图如下:

  

Peg表示柱子类

Disks:vector<int>表示这个柱子上的所有盘子

Name:int表示柱子的名称。我将柱子将0,1,2进行了编号。以便接下来的运算

Disk表示盘子类

       Now:int   表示这个盘子现在所在的柱子

       WantTo:int      表示这个盘子将要移到哪个柱子上去

 

CLoopMove里就是有关用循环移盘子的算法

       对外的唯一接口就是LoopMove(int n):void ,参数n表示有几个盘子。

      

下面详细说明这个函数的实现过程:

1.先申明三个柱子对象

       Peg p0(0);      //起始柱

       Peg p1(1);      //目标柱

       Peg p2(2);      //辅助柱

然后将所以的盘子放在p1上

       为了操作方便,将这个三个变量放在数组中

Peg pegs[3]={p0,p1,p2};

 

2.接下来定义所有的盘子,并且现在盘子都在柱子0上。

       vector<Disk> disks;

       for (int i=0; i<n; ++i)

       {

              disks.push_back(Disk(0));

       }

 

3.由于用数学归纳法,很容易得出移动n个盘子的步数是2n-1,所以只需要循环这么多次就完成任务(如果不计算次数,也可以用死循环,并且在循环内判断游戏是否结束)。在每次循环中,       都调用FindMove()方法,来更新每个盘子的WantTo属性。具体的做法是:

       1.最大的盘子,也就是第n-1个盘子的目标肯定是p1

       2.循环从n-2个盘子到第0个盘子

              在循环中判断,比每个盘子大一的盘子是否已移到位。

              如果没有到位,则它应该移到“第三者”上去

                     disks[i].WantTo = 3 - disks[i+1].Now - disks[i+1].WantTo;  //这句代码最经典

                     比如,n-1号盘现在在0号柱上,要移到1号柱子上,那么n-2号柱子则应在2号柱上

              如果已到位,则这个盘子也应在这个柱子上

                     disks[i].WantTo = disks[i+1].WantTo;

 

4.将FindMove调用完后,就只需要从最小的盘子开始访问,看哪个需要移动,就移动之。

 

上面就是所有的程序结构,可以看出还有很大的优化空间,不用每次都去调用FindMove判断。

并且我现在的这个FindMove算法可以支持半自动。也就是不一定非要从所有的盘在p0上这个状态开始,可以从任何状态开始(当然小盘必须在大盘上面)。

 

在项目中,用递归的汉斯塔来进行比较,发现这个循环版本没有错误。代码下载:《非递归汉斯塔