您的位置:首页 > 其它

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下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: