Rush Hour
是一款滑板游戏(这个词并不陌生,常见的滑板游戏还有华容道、八数码等)。规则很简单,看这里的图就明白了。
首先Breadth first search
的解法是很容易想到的,但性能不够理想,而启发函数也很难设计。Pearls of Functional Algorithm Design
里有一节介绍了一个根据计划求解的方法。
我们人在求解的时候,一般是这样想的:“要把0号车移动到终点,要先移开路上的6、7号车。”“要把6号车移开,可以通过移开5号车来实现,也可以移开8号车。”“要移开5号车,要先移开3、4号车。”等等。
求解这个问题时,我们可以让程序也按照人这样进行思考:要把0号车移动到终点,途中要依次经过19号格和20号格。19号格被6号车占据,可以让6号车依次经过26号格和33号格。26号格被8号车占据,可以把它移动到23号格来腾出位子……
类似于Depth first search
使用栈维护候选状态,Breadth first search
使用队列维护候选状态,该算法维护双端队列,可能如你所预料的,状态的扩展方式揉和了
Depth
与 Breadth
两种方式。
一个状态不仅要表示棋盘布局,还要表示一个计划,计划中的每个步骤要依次执行。
比如游戏获胜的计划是把0号车移动到19,再把0号车移动到20(注意这两步有顺序),简记为(0,19) (0,20)
。其中(0,19)
的计划有两个,只要完成其中一个即可:
(6,26) (6,33)
(5,4) (5,3)
其中 (6,26)
的计划是:
(8,23)
其中 (6,33)
的计划是:
- ……
……
该搜索算法的初始状态就是初始棋盘,计划是
(0,0当前位置右移1格)
(0,0当前位置右移2格)
(0,0当前位置右移3格)
……直到 (0,出口)
。
每次从队列头部取出一个状态 p
进行扩展。其中一种扩展方式和 Breadth
几乎雷同,把一辆车移动一格,生成的状态 q
放入队列尾部。只是要注意 q
的计划依然是
(0,q中0的位置右移1格)
(0,q中0位置右移2格)
(0,q中0位置右移3格)
……直到
(0,出口)
。也就是说,p
的计划被完全忽略了。该过程对应代码中的 bsuccs
。
另外一种方式比较麻烦,需要考虑 p
的计划。
首先要知道计划是可以 变具体的
,也就是说计划的第一步
s
如果没法直接达成(即不能通过把一辆车移动一格达到),
那么这个计划就可以 具体化
。方法是看 s
可以由什么计划来达成(比如把另一辆车挪开腾出位子让 s
对应的车占据)。当然,这个 具体化
过程可能一步就能完成(只挪开一辆车),也可能需要很多步(要挪开很多车),相当于递归展开第一步。
我们要做的就是 具体化
s
使得新计划
s0
的第一步能够直接达成,把达成后得到的状态
s0'
放入队列头部。 当然,具体化
的方案可能不止一种,这种情况下我们要考虑所有 具体化
方案
s0
s1
s2
……它们对应的转移
s0'
s1'
s2'
……要全部放到队列头部。该过程对应代码中的
asuccs
。
代码几乎抄自 Pearls of Functional Algorithm Design
:
1 | {- |