广度优先搜索和深度优先搜索都属于盲目型搜索算法,在选择下一个搜索节点的时候没有一个准则,去选择最优的。很多情况下需要会穷举整个空间,因此只适用于规模较小的搜索。因此需要用到另外一种算法---启发式搜索。
一、A*算法原理
启发式搜索就是在状态空间中对每一个搜索位置进行评估,得到最好的位置,再从这个位置进行搜素直到目标。这样可以省略大量无谓的搜索路径。在启发式搜索中,对位置的估计尤其重要,采用不同的估计函数会有不同的效果。 启发式搜索中的估价函数为: f(n) = g(n) + h(n) f(n):节点n的估价函数。 g(n):从起始点到n节点的实际代价。 h(n):从节点n到目标节点最佳路径的估计代价。 在这里,h(n)便是估价函数,体现了搜索的启发信息。g(n)是已知的。 启发式搜索有很多算法:局部最优搜索、最优优先搜索算法等等。A星算法也是。局部优先搜索是在搜索过程中选择最佳节点后舍弃其他的兄弟节点,其很明显的缺陷是可能舍弃了最好的节点。最好优先则没有舍弃节点,有效地防止了最佳节点的丢失。A星算法是一种最好优先算法。只不过要加上一些特定的约束条件。在一些问题求解时,希望可以求解出状态空间搜索的最短路径。
A*算法的估价函数可表示为: f'(n) = g'(n) + h'(n) 这里,f'(n)是估价函数,g'(n)是起点到终点的最短路径值,h'(n)是n到目标的最短路经的启发值。由于这个f'(n)其实是无法预先知道的,所以我们用前面的估价函数f(n)做近似。g(n)代替g'(n),但g(n)>=g'(n)才可(大多数情况下都是满足的,可以不用考虑),h(n)代替h'(n),但h(n)<=h'(n)才可(这一点特别的重要),即小于n到目标的实际代价值。可以证明应用这样的估价函数是可以找到最短路径的,也就是可采纳的。我们说应用这种估价函数的最好优先算法就是A*算法,否则就不是A*算法。在这种情况下,搜索的范围大,效率低,但是能得到最优解。如果估价值>实际代价,搜索范围小,效率高,但是不能保证得到最优解。 注意:估价值与实际代价值越接近,估价函数选取得就越好。 关于h(n)启发函数的信息性的问题 h(n)的信息性通俗点说其实就是在估计一个节点的值时的约束条件,如果信息越多或约束条件越多则排除的节点就越多,估价函数越好或说这个算法越好。这就是为什么广度优先算法的那么臭的原因了,谁叫它的h(n)=0,一点启发信息都没有。但在游戏开发中由于实时性的问题,h(n)的信息越多,它的计算量就越大,耗费的时间就越多。就应该适当的减小h(n)的信息,即减小约束条件。但算法的准确性就差了,这里就有一个平衡的问题。 关于启发函数h(n)的相容性 如果h(n)满足h(n1) - h(n2) <= c(n1,n2),其中c(n1,n2)是n1节点到n2节点的实际代价,则称h(n)是相容的。也就是说状态转移时,下界h的减小值最多等于状态转移的实际代价,其实就是要求h不能减小得太快。 那么,对于节点n1以及其后继节点n2,有: f(n1) = g(n1) + h(n1) f(n2) = g(n2) + h(n2) 又由于: g(n2) = g(n1) + c(n1,n2),推导出: f(n2) = g(n1) + c(n1,n2) + h(n2) 如果h(n)是相容的,则有h(n1) <= h(n2) + c(n1,n2),两边同时加上g(n1),推导出: h(n1) + g(n1) <= h(n2) + g(n2) + c(n1,n2),也就是: f(n1) <= f(n2) 可以看出,如果启发函数h(n)相容,那么估价函数f(n)单调非递减。就是说按照此算法搜索最先生成的路径一定是最短的。这样的话,搜索过程中就不再需要维持一个CLOSED表,只需维护一个已访问节点表OPEN表。 在选择启发函数的时候,最好选择相容的启发式函数。 A星算法步骤: 1,把起始格添加到开启列表。 二、A*算法编程实现 本文通过迷宫寻路实例,实现了A*算法的C++类封装。 迷宫地图: 起始点是绿色方格,目标点是景色方格,蓝色方格表示障碍物,不可通过。 在搜索过程中,横向移动或者纵向移动的代价都是10,对角线上的移动代价是14.需要注意的是:障碍物的角落不可通过。 将迷宫信息写在一个文本文件里面: 第一行的两个整数代表迷宫的行数和列数。第二行是起始点和目标点的坐标(x,y)。x是所在的行,y是所在的列(均以0开始计算)。 接下来便是迷宫的具体信息,0表示可以通过,1表示障碍物。 下面贴出代码,如有错误,欢迎批评指正(邮箱:xiajunhust@gmail.com)。 程序代码(C++): AStarPathFinding.h:
AStarPathFnding.cpp:
#include "AStarPathFinding.h"
#include <iostream>
#include <cstdio> #include <cmath> #include <string> #include <fstream>
using namespace std;
const int MaxDistance = 9999;
AStarPathFinding::AStarPathFinding(char* mapFileName)
:m_steps(0) { m_mapFileName = (char *)malloc((strlen(mapFileName) + 1) * sizeof(char)); strcpy(m_mapFileName,mapFileName); }
AStarPathFinding::~AStarPathFinding()
{ free(m_mapFileName);
//千万不能有这句代码,因为startNode已加入OPEN表,会在释放OPEN表
//的时候释放,否则会造成重复释放,出现bug //delete startNode; delete endNode;
释放迷宫布局数组:注意多维数组空间释放
for (int i = 0;i < m_rows;++i) { delete[] m_maze[i]; } delete[] m_maze;
for (int i = 0;i < m_rows;++i)
{ delete[] m_path[i]; } delete[] m_path;
//释放OPEN表以及CLOSED表内存空间
vector<Node*>::iterator iter; for (iter = OPENTable.begin();iter != OPENTable.end();++iter) { delete (*iter); } OPENTable.clear();
vector<Node*>::iterator iter2;
for (iter2 = CLOSEDTable.begin();iter2 != CLOSEDTable.end();++iter2) { delete (*iter2); } CLOSEDTable.clear(); }
void AStarPathFinding::init()
{ dx[0] =dx[4] = dx[5] = -1; dx[1] =dx[3] = 0; dx[2] =dx[6] = dx[7] = 1;
dy[3] = dy[4] = dy[7] = -1;
dy[0] =dy[2] = 0; dy[1] =dy[5] = dy[6] = 1;
readMap();
//分配空间
m_path = new int *[m_rows]; for (int i = 0;i < m_rows;++i) { m_path[i] = new int[m_cols]; }
startNode = new Node;
startNode->x = m_startX; startNode->y = m_startY; startNode->g = 0; startNode->h = judge(startNode->x,startNode->y); startNode->f = startNode->g + startNode->h; startNode->father = NULL;
endNode = new Node;
endNode->x = m_endX; endNode->y = m_endY; endNode->father = NULL; }
bool AStarPathFinding::pathFinding()
{ //判断起始点和目标点是否是同一点 if (m_startX == m_endX && m_startY == m_endY) { cout << "WARNNING : The start point is the same as th destination " << endl; return true; }
OPENTable.push_back(startNode);//起始点装入OPEN表
//对vector中元素进行排序:将最后一个元素加入原本已序的heap内
push_heap(OPENTable.begin(),OPENTable.end(),HeapCompare_f());
Node *tempNode = new Node;
//开始遍历
for (;;) { if (OPENTable.empty())//判断OPEN表是否为空 { cout << "ERROR : unable to find the destination" << endl; return false; }
tempNode = OPENTable.front();//注意:OPEN表为空会导致未定义行为
++m_steps; //将第一个元素移到最后,并将剩余区间重新排序,组成新的heap pop_heap(OPENTable.begin(),OPENTable.end(),HeapCompare_f()); OPENTable.pop_back();//删除最后一个元素
//判断是否已经搜寻到目标节点
if (tempNode->x == m_endX && tempNode->y == m_endY) { cout << "OK : success to find the destination" << endl; endNode->g = tempNode->g; endNode->h = tempNode->h; endNode->f = tempNode->f; endNode->father = tempNode->father;
generatePath();
return true;
} for (int i = 0;i < 8;++i)//针对每个子节点 { int nextX = tempNode->x + dx[i]; int nextY = tempNode->y + dy[i]; if (isIllegle(nextX,nextY)) { //注意:障碍物角落不能直接通过 if (1 == *(*(m_maze + tempNode->x) + nextY) || 1 == *(*(m_maze + nextX) + tempNode->y)) { continue; } //计算此子节点的g值 int newGVal; if (!dx[i] && !dy[i])//位于对角线上 { newGVal = tempNode->g + 14; } else newGVal = tempNode->g + 10;
//搜索OPEN表,判断此点是否在OPEN表中
vector<Node*>::iterator OPENTableResult; for (OPENTableResult = OPENTable.begin(); OPENTableResult != OPENTable.end();++OPENTableResult) { if ((*OPENTableResult)->x == nextX && (*OPENTableResult)->y == nextY) { break; } }
//此子节点已经存在于OPEN表中
if (OPENTableResult != OPENTable.end()) { //OPEN表中节点的g值已经是最优的,则跳过此节点 if ((*OPENTableResult)->g <= newGVal) { continue; } }
//搜索CLOSED表,判断此节点是否已经存在于其中
vector<Node*>::iterator CLOSEDTableResult; for (CLOSEDTableResult = CLOSEDTable.begin(); CLOSEDTableResult != CLOSEDTable.end();++CLOSEDTableResult) { if ((*CLOSEDTableResult)->x == nextX && (*CLOSEDTableResult)->y == nextY) { break; } }
//此节点已经存在于CLOSED表中
if (CLOSEDTableResult != CLOSEDTable.end()) { //CLOSED表中的节点已经是最优的,则跳过 if ((*CLOSEDTableResult)->g <= newGVal) { continue; } }
//此节点是迄今为止的最优节点
Node *bestNode = new Node; bestNode->x = nextX; bestNode->y = nextY; bestNode->father = tempNode; bestNode->g = newGVal; bestNode->h = judge(nextX,nextY); bestNode->f = bestNode->g + bestNode->h;
//如果已经存在于CLOSED表中,将其移除
if (CLOSEDTableResult != CLOSEDTable.end()) { delete (*CLOSEDTableResult); CLOSEDTable.erase(CLOSEDTableResult); }
//如果已经存在于OPEN表,更新
if (OPENTableResult != OPENTable.end()) { delete (*OPENTableResult); OPENTable.erase(OPENTableResult);
//重新建堆,实现排序。注意不能用sort_heap,因为如果容器为空的话会出现bug
make_heap(OPENTable.begin(),OPENTable.end(),HeapCompare_f()); }
OPENTable.push_back(bestNode);//将最优节点放入OPEN表
push_heap(OPENTable.begin(),OPENTable.end(),HeapCompare_f());//重新排序
} }
CLOSEDTable.push_back(tempNode);
}
return false;
}
void AStarPathFinding::generatePath()
{
Node *nodeChild = endNode;
Node *nodeParent = endNode->father; do { *(*(m_path + nodeChild->x) + nodeChild->y) = NODE_PATH;//标记为路径上的点 nodeChild = nodeParent; nodeParent = nodeParent->father; } while (nodeChild != startNode);
*(*(m_path + startNode->x) + startNode->y) = NODE_PATH;//标记为路径上的点
}
void AStarPathFinding::printPath()
{ cout << "The path is " << endl; for (int i = 0;i < m_rows;++i) { for (int j = 0;j < m_cols;++j) { if (NODE_PATH == *(*(m_path + i) + j)) { cout << "# "; } else cout << *(*(m_maze + i) + j) << " "; } cout << endl; }
cout << "搜索总步数:" << m_steps << endl;
}
bool AStarPathFinding::readMap()
{ //从文本文件读取迷宫布局信息 ifstream mapFileStream(m_mapFileName); if (!mapFileStream) { cerr << "ERROR : unable to open map file" << endl; return false; }
mapFileStream >> m_rows >> m_cols;
//多维数组空间分配
m_maze = new int *[m_rows]; for (int i = 0;i < m_rows;++i) { m_maze[i] = new int[m_cols]; }
mapFileStream >> m_startX >> m_startY;
mapFileStream >> m_endX >> m_endY;
for (int i = 0;i < m_rows;++i)
{ for (int j = 0;j < m_cols;++j) { mapFileStream >> *(*(m_maze + i) + j); } }
return true;
}
int AStarPathFinding::judge(int x, int y)
{ return (10 * (abs(m_endX - x) + abs(m_endY - y))); }
bool AStarPathFinding::isIllegle(int x, int y)
{ if (x >= 0 && x < m_rows && y >= 0 && y < m_cols && *(*(m_maze + x) + y) == 0) return true; else return false; }
针对上面的迷宫问题的搜索结果:
![]()
注意:'#'代表该节点是路径上的点。
|
A*算法原理
最新推荐文章于 2024-06-28 16:13:40 发布