最近在看了大学算法的教程后,深深地理解到了递归算法在某些情况下的局限性,于是就想到了两年前自己做的汉斯塔的项目中的递归算法。能否将其变成循环算法呢?
为了简化,我是在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上这个状态开始,可以从任何状态开始(当然小盘必须在大盘上面)。
在项目中,用递归的汉斯塔来进行比较,发现这个循环版本没有错误。代码下载:《非递归汉斯塔》