8 Puzzle/8 数码问题
2010-01-23 10:54
357 查看
这些天一直在研究8数码问题,用C++实现了A*。并且用了C++的模板使得它也能够处理15数码的问题。但是15数码的问题的难度超乎了我的想象。A*不管用了。一个更好方案是使用IDA*算法。前几天看了篇论文,发现了一个增强版的IDA*算法。所以决定使用这个威力加强版IDA*算法来处理15数码的问题。在这之前,我决定写一下处理8数码问题的一些心得。
我的8数码问题的实现涉及到的一些东西。我简要地把它们列了一下:
1. 8数码问题的计算机表示
使用二维数组来表示,具体下文会说到。
2. 曼哈顿距离 定义请看百度百科
用来计算棋盘到棋盘的距离。棋盘1到棋盘2的距离就是棋盘1中每个数字的位置与棋盘2中对应数字的位置的曼哈顿距离的总和。
3. A*算法
A*算法的大致思想是先根据初始棋盘随便走几步来生成一些棋盘,然后依据启发函数找到最接近最终棋盘的棋盘B,再从棋盘B出发生成些棋盘,用启发函数找到最接近最终棋盘的那个。以此类推直到找到的棋盘就是最终棋盘。
4.stl中的priority_queue和set
C++标准模板库中的两个容器,优先队列和集合。
5.C++函数对象
用来定制stl中的容器。
6.C++模板
使用模板的好处就是一个类可同时应用于8数码和15数码问题。
我定义了模板类SlidingPuzzleNode<NP>来表示每个棋盘节点。每个棋盘应该具有如下的属性:
1. 棋盘的表示
我定义了一个二维数组来表示棋盘。上图中的3X3的棋盘可以这样定义char board[3][3]={
{1,7,8},
{6,5,2},
{4,3,0}
};
棋盘中的空格用0来表示。
2. 启发函数的计算
启发函数f由两部分构成。到最终棋盘的距离加上到初始棋盘的最短距离。到最终棋盘的距离使用曼哈顿距离,到初始棋盘的距离是从初始棋盘到当前棋盘走的最少步数。
3. 下一步走的棋盘节点
我定义了vector<SlidingPuzzleNode<NP>*> neighbors;来存放下步走的棋盘。
4.上一步棋盘节点
我定义了SlidingPuzzleNode* parent;来表示上一步的棋盘节点。
5. 区分每个棋盘的ID
下文中介绍。
[b]6. 判断是否为目标棋盘[/b]
用棋盘的ID判断。
A*算法本身不难理解。它有两个容器openset和closedset。openset存放待考察的棋盘,closedset存放考察过的棋盘。每次从openset中取出f()最小的棋盘,(f()是启发函数,它由g()和h()构成。g()是初始棋盘到当前棋盘走的最小步数,h()是当前棋盘到目标棋盘的启发式距离。h()的值越小,它离目标棋盘的距离越近。f()值最小表示它可能是初始棋盘到目标棋盘的最短路径中的某个节点。)如果它是最终棋盘则算法结束。否则放入closedset中。再得到它的下一步棋盘k,如果k已经在closedset中,表明这个棋盘k已经考察过了。如果k已在openset中出现过并且k的g()比openset中的g()小,更新openset中的g()值为k的g()的值。如果这些条件都不满足,直接放入openset中。wiki上的伪代码:
//start为初始节点,goal为最终节点 function A*(start,goal) closedset := the empty set // 存放已经考察过的节点的集合 openset := set containing the initial node // 存放需要被考察的节点的集合 g_score[start] := 0 // 到初始节点的最优距离 h_score[start] := heuristic_estimate_of_distance(start, goal) // 估算start到goal的启发距离 f_score[start] := h_score[start] // g_score[start]+h_score[start] while openset is not empty x := the node in openset having the lowest f_score[] value if x = goal return success remove x from openset add x to closedset foreach y in neighbor_nodes(x) if y in closedset continue //计算y到初始节点的距离 tentative_g_score := g_score[x] + dist_between(x,y) if y not in openset add y to openset tentative_is_better := true elseif tentative_g_score < g_score[y] tentative_is_better := true else tentative_is_better := false //当前x的邻居节点y比openset中的y有更小的g_score值,则更新openset中的y if tentative_is_better = true came_from[y] := x g_score[y] := tentative_g_score h_score[y] := heuristic_estimate_of_distance(y, goal) f_score[y] := g_score[y] + h_score[y] return failure
可以看到A*算法需要对openset和closedset这两个集合进行频繁地插入和查找操作。所以openset和closedset使用哪种数据结构来表示非常重要。启发函数的计算和取得下一步的棋盘等操作由棋盘节点SlidingPuzzleNode类自己来完成,在我自己实现的A*算法中就不需要操心了,这是面向对象的好处啊。下面的openset和closedset的表示:
由于openset的作用只是每次取f()最小的棋盘,所以使用stl的优先队列priority_queue表示。priority_queue使用最大堆来实现,每次返回权值最大的元素。为此需要提供一个函数对象Greator把它变为最小堆。Greator很简单,源码如下:
template<size_t NP> class Greator{ public: bool operator()(SlidingPuzzleNode<NP>* p1,SlidingPuzzleNode<NP>* p2){ return p1->f()>p2->f(); } };
priority_queue使用vector为底层容器,vector的一个很大特点就是当它已满时,会开辟一个更大的空间,把原来的元素通通复制过去。所以priority_queue存放的元素最好是复制代价很小的值类型或指针。所以openset的定义是这样子的:
priority_queue<SlidingPuzzleNode<NP>*,vector<SlidingPuzzleNode<NP>*>,Greator<NP> > openset;
closedset由于要进行频繁地查找操作,我使用stl中的set表示。set使用红黑树来实现,每次查找的代价为O(lgn)。为了使用set,还必须提供<操作符。我使用函数对象Less,源码如下:
template<size_t NP> class Less{ public: bool operator()(const SlidingPuzzleNode<NP>* p1,const SlidingPuzzleNode<NP>* p2){ return p1->hash()<p2->hash(); } };
closedset的定义:
set<SlidingPuzzleNode<NP>*,Less<NP> > closedset;
hash()函数返回每个棋盘的ID,两个不同的棋盘不可能返回相同的ID。为了达到这个目的,hash()函数的实现为取每个棋盘的数字序列。例如这个棋盘
的数字序列为123804765。
有了这些就可以实现A*算法了:
template<byte NP> bool SlidingPuzzle<NP>::A_star() { while(!openset.empty()){ SlidingPuzzleNode<NP>* x=const_cast<SlidingPuzzleNode<NP>*>(openset.top()); /*如果x是目标棋盘*/ if(x->reach_goal()){ make_steps(x); return true; } /*从openset中删除*/ openset.pop(); /*从_openset中删除*/ typename set<SlidingPuzzleNode<NP>*,Less<NP> >::iterator it; it=_openset.find(x); _openset.erase(it); /*插入closedset中*/ closedset.insert(x); /*获得x的下一步棋盘*/ vector<SlidingPuzzleNode<NP>*>& x_neighbors=x->get_neighbors(); /*对于当前棋盘的所有下一步棋盘做*/ for(byte i=0;i<x_neighbors.size();i++){ /*如果出现在closedset*/ if((closedset.find(x_neighbors[i])!=closedset.end())){ continue; } it=_openset.find(x_neighbors[i]); /*如果没有在openset中 插入openset*/ if((it==_openset.end())){ _openset.insert(x_neighbors[i]); openset.push(x_neighbors[i]); } /*如果已在openset中,并且openset中的代价较大*/ else if((*it)->g()>x_neighbors[i]->g()){ SlidingPuzzleNode<NP>* e=const_cast<SlidingPuzzleNode<NP>*>(*it); e->parent=x_neighbors[i]->parent; e->cost=x_neighbors[i]->cost; } } } return false; }
完整的源代码下载
csdn下载
google下载
相关文章推荐
- HDU 1043 八数码问题 A*搜索+康拓展开+逆序对判断+路径输出
- 八数码问题——A*大法好
- 八数码问题
- 八数码问题(紫薯P199)
- 八数码问题
- 启发式搜索程序设计-八数码问题
- BFS:八数码问题
- hdu 6048 Puzzle 思维(8数码问题
- hdu 6048 Puzzle 思维(8数码问题
- hdu(4021)八数码的解是否存在问题
- 【启发式搜索】八数码问题
- 八数码问题的可解行
- HDU 1043 八数码问题的多种解法
- 数码问题合集
- 路径寻找——八数码问题
- 【POJ1077】Eight 八数码问题,解题报告+思路+代码
- poj 1077 bfs+康托展开(8数码问题)
- 八数码问题
- Poj 1077 Eight 八数码问题 (搜索)
- 搜索训练1 [8数码问题]