八数码的八重境界

Eight

Time Limit:1000MS    MemoryLimit: 65536K  Special Judge

Description                                 

The 15-puzzle has been aroundfor over 100 years; even if youdon't know it by that name, you've seen it. Itis constructed with15 sliding tiles, each with a number from 1 to 15 on it,and allpacked into a 4 by 4 frame with one tile missing. Let's callthemissing tile 'x'; the object of the puzzle is to arrange thetiles so that theyare ordered as:

  4

  8

  10 1112

 13 14 15 x

where the only legal operationis to exchange 'x' with one of thetiles with which it shares an edge. As anexample, the followingsequence of moves solves a slightly scrambled puzzle:

        4

        8

  x1012    910  x12   9 10 1112    9 10 1112

13 14 11 15   13 14 1115  13 14  x15   13 1415 x

          r->          d->          r->

The letters in the previousrow indicate which neighbor of the'x' tile is swapped with the 'x' tile ateach step; legal values are'r','l','u' and 'd', for right, left, up, anddown,respectively.

Not all puzzles can be solved;in 1870, a man named Sam Loyd wasfamous for distributing an unsolvable versionof the puzzle, and

frustrating many people. Infact, all you have to do to make aregular puzzle into an unsolvable one is toswap two tiles (notcounting the missing 'x' tile, of course).

In this problem, you willwrite a program for solving the lesswell-known 8-puzzle, composed of tiles ona three by threearrangement.

Input

You will receive a descriptionof a configuration of the 8puzzle. The description is just a list of the tilesin their initialpositions, with the rows listed from top to bottom, and thetileslisted from left to right within a row, where the tiles arerepresentedby numbers 1 to 8, plus 'x'. For example, thispuzzle

  3

  6

  8

is described by this list:

 1 2 3 x 4 6 7 5 8

Output

You will print to standardoutput either the word ``unsolvable'',if the puzzle has no solution, or astring consisting entirely ofthe letters 'r', 'l', 'u' and 'd' that describesa series of movesthat produce a solution. The string should include no spacesandstart at the beginning of the line.

Sample Input

  8

Sample Output

ullddrurdllurdruldr

 

  这个题目是SpecialJudge,任意找出一组移法就行,但是很多时候,我们需要找到步数最少的移法,所以,这里以步数最少的移法为目的。真正优化这个问题涉及到很多,比如A*、全排列哈希、堆优化等。一境界一代码,咱们一个境界一个境界走,慢慢优化这个经典问题,当然,我不是那么无聊,不会把所有境界都列出代码……

 

  境界一、 暴力广搜+STL

  开始的时候,自然考虑用最直观的广搜,因为状态最多不超过40万,计算机还是可以接受的,由于广搜需要记录状态,并且需要判重,所以可以每次图的状态转换为一个字符串,然后存储在stl中的容器set中,通过set的特殊功能进行判重,由于set的内部实现是红黑树,每次插入或者查找的复杂度为Log(n),所以,如果整个算法遍历了所有状态,所需要的复杂度为n*Log(n),在百万左右,可以被计算机接受,由于对string操作比较费时,加上stl全面性导致速度不够快,所以计算比较费时,这样的代码只能保证在10秒内解决任何问题。但,明显效率不够高。POJ上要求是1秒,无法通过,第一次的代码见Code1.cpp。

 

  境界二、广搜+哈希

  考虑到费时主要在STL,对于大规模的遍历,用到了ST的set和string,在效率上的损失是很大的,因此,现在面临一个严重的问题,必须自己判重,为了效率,自然是自己做hash。有点麻烦,hash函数不好想,实际上是9!种排列,需要每种排列对应一个数字。网上搜索,得知了排列和数字的对应关系。取n!为基数,状态第n位的逆序值为哈希值第n位数。对于空格,取其为9,再乘以8!。例如,1 3 7 24 6 9 5 8 的哈希值等于:0*0! + 2*1! + 0*2! + 1*3! + 3*4! +1*5! +0*6! + 1*7! + 0*8! <9!具体的原因可以去查查一些数学书,其中1 2 34 5 6 78 9 的哈希值是0 最小,9 8 7 6 54 3 2 1 的哈希值是(9!-1)最大。而其他值都在0 到(9!-1)中,且均唯一。然后去掉一切STL之后,甚至包括String之后,得到单向广搜+Hash的代码,算法已经可以在三秒钟解决问题,可是还是不够快!POJ时限是1秒,后来做了简单的更改,将路径记录方法由字符串改为单个字符,并记录父节点,得到解,这次提交,266ms是解决单问题的上限。当然,还有一个修改的小技巧,就是逆序对数不会改变,通过这个,可以直接判断某输入是否有可行解。由于对于单组最坏情况的输入,此种优化不会起作用,所以不会减少单组输入的时间上限,经过这些优化的代码见Code2.cpp。

 

  境界三、广搜+哈希+打表

  好,问题可以在200—300ms间解决,可是,这里我们注意到一个问题,最坏情况下,可能搜索了所有可达状态,都无法找到解。如果这个题目有多次输入的话,每次都需要大量的计算。其实,这里可以从反方向考虑下,从最终需要的状态,比如是POJ1077需要的那种情况,反着走,可达的情况是固定的。可以用上面说的那种相应的Hash的方法,找到所有可达状态对应的值,用一个bool型的表,将可达状态的相应值打表记录,用“境界三”相似的方法记录路径,打入表中。然后,一次打表结束后,每次输入,直接调用结果!这样,无论输入多少种情况,一次成功,后面在O(1)的时间中就能得到结果!这样,对于ZOJ的多组输入,有致命的帮助!此境界代码改动不大,不再给出,下同。

 

  境界四、双向广搜+哈希
  Hash,不再赘述,现在,我们来进行进一步的优化,为了减少状态的膨胀,自然而然的想到了双向广搜,从输入状态点和目标状态1 2 34 5 6 7 89同时开始搜索,当某方向遇到另一个方向搜索过的状态的时候,则搜索成功,两个方向对接,得到最后结果,如果某方向遍历彻底,仍然没有碰上另一方向,则无法完成,代码不再给出,原因见上……

 

  境界五、A*+哈希+简单估价函数
  用到广搜,就可以想到能用经典的A*解决,用深度作为g(n),剩下的自然是启发函数了。对于八数码,启发函数可以用两种状态不同数字的数目。接下来就是A*的套路,A*的具体思想不再赘述,因为人工智能课本肯定比我讲的清楚。但是必须得注意到,A*需要满足两个条件:

1.h(n)>h'(n),h'(n)为从当前节点到目标点的实际的最优代价值。

2.每次扩展的节点的f值大于等于父节点的f值小。

自然,我们得验证下我们的启发函数,h验证比较简单不用说,由于g是深度,每次都会较父节点增1。再看h,认识上,我们只需要将h看成真正的“八数码”,将空格看空。这里,就会发现,每移动一次,最多使得一个数字回归,或者说不在位减一个。h最多减小1,而g认为是深度,每次会增加1。所以,f=g+h,自然非递减,这样,满足了A*的两个条件,可以用A*了!此境界代码也不列出,因为后几个加了优化的代码,是此境界的父集,或者说升级版。既然逻辑上一样,我们就只给出级别最高的。

  境界六、A*+哈希+曼哈顿距离

  A*的核心在启发函数上,境界五若想再提升,先想到的是启发函数。这里,曼哈顿距离可以用来作为我们的启发函数。曼哈顿距离听起来神神秘秘,其实不过是“绝对轴距总和”,用到八数码上,相当与将所有数字归位需要的最少移动次数总和。作为启发函数,自然需要满足“境界五”提到的那两个条件。现在来看这个曼哈顿距离,第一个条件自然满足。对于第二个,因为空格被我们剥离出去,所以交换的时候只关心交换的那个数字,它至多向目标前进1,而深度作为g每次是增加1的,这样g+h至少和原来相等,那么,第二个条件也满足了。A*可用了,而且,有了个更加优化的启发函数。因为还可以升级,所以……代码不给出。

 

  境界七、A*+哈希+曼哈顿距离+小顶堆

  经过上面优化后,我们发现了A*也有些鸡肋的地方,因为需要每次找到所谓Open表中f最小的元素,如果每次排序,那么排序的工作量可以说是很大的,即使是快排,程序也不够快!这里,可以想到,由于需要动态的添加元素,动态的得到程序的最小值,我们可以维护一个小顶堆,这样的效果就是。每次取最小元素的时候,不是用一个n*Log(n)的排序,而是用log(n)的查找和调整堆,好,算法又向前迈进了一大步。这里,我们终于要给出代码了,代码参见Code3.Cpp。

 

  境界八、IDA*+曼哈顿距离

  IDA*即迭代加深的A*搜索,实现代码是最简练的,无须状态判重,无需估价排序。那么就用不到哈希表,堆上也不必应用,空间需求变的超级少。效率上,应用了曼哈顿距离。同时可以根据深度和h值,在找最优解的时候,对超过目前最优解的地方进行剪枝,这可以导致搜索深度的急剧减少,所以,这,是一个致命的剪枝!因此,IDA*大部分时候比A*还要快,可以说是A*的一个优化版本!代码参见Code4.cpp。到此,到了我的境界八……请老师再将境界进行升级……还有,前文中也许会有很多疏漏之处,望老师给以修正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值