《算法的乐趣》——华容道游戏
这一章来简单的介绍一下华容道游戏及如何利用算法来计算出其最优步数。
首先对于华容道游戏,我们来介绍一下它的规则。
有点类似于拼图,本质上这是一个5x4的矩阵,我们的目标就是让曹操(2x2)的矩阵从5x4的矩阵中的第5行的3、4列走出来。
游戏规则很简单,但是想要用最小的步数来完成就不那么容易了,这便是我们下面要着力解决的问题。
其实对于学过dfs和bfs的读者,可能已经想到解决的思路了,其实可以搜索所有的状态,然后形成状态树(这和笔者前面介绍的双人对局的博弈树是一个东西),在建树的过程中,我们设置参数step来记录某个节点(即一种棋局状态)的步数,遍历到了所有棋局状态后自然可以轻松找到最少步骤。
下面我们尝试来完善这个过程的数据结构。首先也是最重要的是,在状态树每个节点记录的不同的棋局状态。容易看到,需要有棋局的状态(我们可以利用二维数组储存)、需要参数step来记录步数。是否还需要一些别的数据呢?
我们能够看到,状态树的建立过程中,一个关键所在是建立父子节点的联系,因此对于某个节点,我们还要储存它的父节点状态,这建立起该节点与上一层树结构的联系;同时,我们还要有参数记录基于改节点的布局,下一步的移动步骤,即生成状态树下一层的一条引线。
由此初步的我们可以写出这样的结构体,来记录某个状态。
struct HRD_GAME_STATE { char board[HIGH][WIDE]; //棋局状态 MOVE_ACTION move; //下一步的移动策略 int step; //移动步数 HRD_GAME_STATE * parent; //父节点 }
容易看到在上文的分析中,我们对“下一步的移动步骤”的描述太过模糊,在编程中我们需要将每个动作量化成一条一条可视化的信息。而对于“移动步骤”来说,第一我们需要知道移动谁,这是一个参量,而第二我们需要知道向哪移动,这又是一个参量。因此在上文数据结构中的move其实还是一个结构体变量。即如下的形式。
typedef struct tagMOVE_ACTION { int heroInx; int dirIdx; }MOVE_ACTION;
类似的,我们进行自上而下的程序设计,对于“每个步骤”中的方向,我们有四个方向的选择,因此我们需要继续构造一个结构体数组,来实现变量dirIdx对四个方向一一对应的描述。
typedef struct tagDIRECTION { int hd; int vd; }DIRECTION; DIRECTION directions[4] = {{0,1}、{0,-1},{1,0},{-1,0}}; //这里是矩阵坐标,上下移动行变列不变,左右移动列边行不变。
初步了解到了解决解决华容道算法的数据结构,其实与博弈树非常类似,下面便是更加重要的剪枝与优化了。
<未完>