用STL实现DFS/BFS算法
——推箱子游戏
(1)
推箱子的游戏想必很多朋友都有玩过,简单地说就是,在一个m*n的范围内有k个箱子和k个目的地,你只能使用推的方法来移动箱子,不能拖,也不能推动两个或以上的箱子,活动范围通常比较狭窄,还有一些不可移动的障碍物,所以有一定难度和可玩性。以下是一个中等难度的推箱子题目:

你可以看到,图的正中央是负责推箱子的“人”(Soko);四周围住的是不可移动的障碍物(Wall),中间也有一些;灰色的是空地(Space);在Soko的右边是一个箱子(Box),由于它不是在目的地上,所以用黄色来表示;它右下方的空地有一个紫红点,表示这是一个停放箱子的目的地(Dest);在Soko的上方、左上方、右上方共有三个蓝色的箱子,表示这三个箱子已经放在目的地上了(不过为了把所有箱子都移动到目的地,这些已经放好的箱子也可以被移开)。所以,这道推箱子题目共有四个箱子和四个目的地。
首先,我们要决定如何在程序中表示推箱子问题的某个状态。我想到最简单的方法就是用一个m*n的二维char数组来表示,数组中的每一个char表示图中的一个格,格子可能是障碍物、人、箱子、目的地或空格,这几种状态也可能重叠,如人可以恰好站在一个目的地上,或者一个箱子恰好放在目的地上。我们用以下几个char来表示可能出现的格子状态:
CharWall = 'W', // Wall
障碍物
CharBox = 'B', // Box
箱子,不在目的地
CharSpace = 'S', // Space
空地,没有任何东西
CharDest = 'D', // Dest
目的地
CharInDest = 'I', //
已放在目的地上的箱子
CharSoko = 'K', // Soko
推箱子的人,不在目的地
CharSokoInDest = 'O', // Soko
推箱子的人,恰好在目的地
CharError = 'E' //
错误状态
用这种方法表示上面的题目,可以得到上图右侧的二维数组,行数和列数均为9。你可以看到左图并不是整整齐齐的二维图,它的最外围有一些缺口,我们在右图中全部用’W’来表示。这是显而易见的,它们没有什么作用,只是为了把图形填充为整齐的二维数组。如果你想用’S’来表示也无所谓,不会影响我们的程序执行,不过我想’W’更合适。
以上是一个推箱子问题状态的文本表示法,或者说是输入输出的方法,便于我们查看问题的状态。但是对于计算机程序处理来说,它并不是理想的表示方法,计算机更善于处理二进制的数字。所以我们还应该用二进制的方式来表示问题的状态,以便于程序的处理。同样的,我们还是使用二维数组来表示,不过这次数组的元素不是char字符,而是二进制的unsigned char。经过简单的分析可以知道,一个格子可以用4个bit来表示所有状态:
FlagWall = 0x01, //
是否障碍物
FlagBox = 0x02, //
是否有箱子
FlagDest = 0x04, //
是否目的地
FlagSoko = 0x08 //
是否有人
在输入和输出问题状态时,需要进行这两种表示法之间的转换。即在输入时,把文本表示法转换为二进制表示法;在输出时,则把二进制表示法转换为文本表示法。这些都不难,后面会列出相关的代码。现在,我们先来给出问题状态类的定义。
和我们前面见到过的数独问题和N皇后问题一样,要使用DFS/BFS算法,我们需要设计问题状态类。对于推箱子问题,我们把问题状态类命名为SokoState,它的定义应该象下面这样:
class SokoState
{
public:
SokoState() : rows_(0), cols_(0) {}
void nextStep(vector<SokoState>&) const;
bool isTarget() const;
bool operator< (const SokoState& other) const;
friend ostream& operator<< (ostream& os, const SokoState& s);
friend istream& operator>> (istream& is, SokoState& s);
private:
struct SokoStep; //
用于记录每一步箱子的移动
int rows_