华容道解题报告
基本思想:
广度优先遍历搜索所有可能棋局。
实现方法:
1.棋子的类型(hrd.h)
#define CHESS_BLANK 0 //空白
#define CHESS_G4 1 //四格棋子
#define CHESS_G2H 2 //横向两格棋子
#define CHESS_G2V 3 //纵向两个棋子
#define CHESS_G1 4 //一格棋子
#define BOUND 5 //边界
可用3位编码表示。
2.布局的表示方法(board.h)
typedef struct Board {
unsigned long long id;
char layout[9][8];
char index[12][2];
} *Board;
id为布局的压缩表示方法。3位表示一个格子的状态,共需60位,使用一个64位的整数表示。gen_id()函数用于由layout数组计算id。
layout为布局的直观表示方法。布局存储在位于中央的layout[2...6][2...5]中。周围保留宽度为2的边界,均填入BOUND类型。(便于行棋时的比较判断)。
index中保存每一枚棋子的横纵座标的索引。共10枚棋子,加两个空格。
3.棋子的属性(board.h)
extern int wh[12][3];
与index对应,保存每一枚棋子的长度、宽度、和类型值。顺序为:
0 1 2 3 4 5 6 7 8 9 10 11
G4, G2V, G2V, G2V, G2V, G2H, G1, G1, G1, G1, BLANK, BLANK
4.采用hash表保存和查询已有的棋局id。(hash.h)
void add_hash(unsigned long long id);
int get_hash(unsigned long long id);
5.逆向的搜索树:每获得新的状态节点都设一个链接指向父节点。(tree.h)
typedef struct TreeNode {
Board board;
struct TreeNode *parent;
} *TNode;
6.队列用于BFS。(queue.h, 循环链表实现)
typedef struct QueueNode {
TNode tnode;
struct QueueNode *next;
} *QNode;
7.搜索:对指定棋局,搜索每一枚棋子是否有可行的移动。若有,产生移动后的棋局,检查是否是新的棋局。如果是,则加入搜索树、队列和hash表。继续下一步搜索。
优化策略:
使用棋局压缩编码进行与先前棋局的比较是提高效率的关键。我开始没有想到,是看到他人的解法才获得启示。hash表可以提供常数的查找时间,hash函数随便写了一个(hash.c),看来还可以。
棋子的移动方式要考虑周全,G4有四种可能,G2H/G2V各有六种可能,G1则有16种可能。需要合理的安排条件语句,避免不必要的开销。
这里只实现了对于标准“横刀立马”布局的搜索,没有做进一步的推广。
题目的规模并不大。Profiling显示调用开销占前三位的是gen_id(), add_hash()和get_hash()。在我的600MHzCPU上求解“横刀立马”81步解法需要0.3s左右,共搜索23822个状态节点。
------------------
源代码: hrd.tar.bz2