又是最重要的32个算法之一。是一种静态路网中求解最短路最有效的直接搜索方法。后面还有更多改进的算法。
和其它的图搜索算法一样,A*潜在地搜索图中一个很大的区域。和Dijkstra一样,A*能用于搜索最短路径。和BFS(Best-First-Search而非Breadth-First-Search)一样,A*能用启发式函数引导它自己。在简单的情况中,它和BFS一样快。1968年发明的A*算法就是把启发式方法(heuristic approaches)如BFS,和常规方法如Dijsktra算法结合在一起的算法。有点不同的是,类似BFS的启发式方法经常给出一个近似解而不是保证最佳解。比较神奇的是,尽管A*基于无法保证最佳解的启发式方法,A*却能保证找到一条最短路径。
成功的秘决在于,它把Dijkstra算法(靠近初始点的结点)和BFS算法(靠近目标点的结点)的信息块结合起来。在讨论A*的标准术语中,g(n)表示从初始结点到任意结点n的代价,h(n)表示从结点n到目标点的启发式评估代价(heuristic estimated cost)。当从初始点向目标点移动时,A*权衡这两者。每次进行主循环时,它检查f(n)最小的结点n,其中f(n) = g(n) + h(n)。
启发式函数h(n)决定了A*算法从任意结点n到目标结点的最小代价评估值。这个函数可以控制A*的行为:
- 一种极端情况,如果h(n)是0,则只有g(n)起作用,此时A*演变成Dijkstra算法,这保证能找到最短路径。
- 如果h(n)经常都比从n移动到目标的实际代价小(或者相等),则A*保证能找到一条最短路径。h(n)越小,A*扩展的结点越多,运行就得越慢。
- 如果h(n)精确地等于从n移动到目标的代价,则A*将会仅仅寻找最佳路径而不扩展别的任何结点,这会运行得非常快。尽管这不可能在所有情况下发生,你仍可以在一些特殊情况下让它们精确地相等(译者:指让h(n)精确地等于实际值)。只要提供完美的信息,A*会运行得很完美,认识这一点很好。
- 如果h(n)有时比从n移动到目标的实际代价高,则A*不能保证找到一条最短路径,但它运行得更快。
- 另一种极端情况,如果h(n)比g(n)大很多,则只有h(n)起作用,A*演变成BFS算法。
<span style="font-size:18px;">/*
A star 算法的基础处理
*/
#ifndef _A_STAR_BASE_H_
#define _A_STAR_BASE_H_
#include "windows.h"
typedef struct _APoint{
int x; // x 坐标
int y; // y 坐标
int type; // 类型
int f; // f = g + h
int g;
int h;
} APoint,*PAPoint;
enum APointType{
APT_UNKNOWN, // 未知状态
APT_OPENED, // 开放列表中
APT_CLOSED, // 关闭列表中
APT_STARTPOINT, // 起始点
APT_ENDPOINT // 结束点
};
class CAStarBase{
public:
CAStarBase();
~CAStarBase();
private:
PAPoint m_pAPointArr;
int m_nAPointArrWidth;
int m_nAPointArrHeight;
PAPoint m_pStartPoint,m_pEndPoint,m_pCurPoint;
char* m_pOldArr;
public:
BOOL Create(char* pDateArr,int nWidth,int nHeight);
void SetStartPoint(int x,int y);
void SetEndPoint(int x,int y);
void SetOpened(int x,int y);
void SetClosed(int x,int y);
void SetCurrent( int x,int y );
void PrintCharArr();
PAPoint CalcNextPoint(PAPoint ptCalc); // 应用迭代的办法进行查询
};
#endif
</span>
<span style="font-size:18px;">#include "stdafx.h"
#include "AStarBase.h"
CAStarBase::CAStarBase()
{
m_pAPointArr = NULL;
m_nAPointArrWidth = 0;
m_nAPointArrHeight = 0;
m_pStartPoint = NULL;
m_pEndPoint = NULL;
m_pCurPoint = NULL;
}
CAStarBase::~CAStarBase()
{
}
BOOL CAStarBase::Create( char* pDateArr,int nWidth,int nHeight )
{
if(!pDateArr) return FALSE;
if(nWidth<1 || nHeight<1) return FALSE;
m_pAPointArr = new APoint[nWidth*nHeight];
if(!m_pAPointArr) return FALSE;
m_pOldArr = pDateArr;
m_nAPointArrWidth = nWidth;
m_nAPointArrHeight = nHeight;
// 初始化数组内容
for ( int y = 0;y<m_nAPointArrHeight;y++)
{
for ( int x=0;x<m_nAPointArrWidth;x++)
{
m_pAPointArr[y*m_nAPointArrWidth+x].x = x;
m_pAPointArr[y*m_nAPointArrWidth+x].y = y;
m_pAPointArr[y*m_nAPointArrWidth+x].g = 0;
m_pAPointArr[y*m_nAPointArrWidth+x].f = 0;
m_pAPointArr[y*m_nAPointArrWidth+x].h = 0;
if ( pDateArr[y*m_nAPointArrWidth+x] == '0')
{
m_pAPointArr[y*m_nAPointArrWidth+x].type = APT_OPENED;
}else if ( pDateArr[y*m_nAPointArrWidth+x] == '1')
{
m_pAPointArr[y*m_nAPointArrWidth+x].type = APT_CLOSED;
}else if ( pDateArr[y*m_nAPointArrWidth+x] == 'S')
{
m_pAPointArr[y*m_nAPointArrWidth+x].type = APT_STARTPOINT;
m_pStartPoint = m_pAPointArr + y*m_nAPointArrWidth+x;
m_pCurPoint = m_pStartPoint;
}else if ( pDateArr[y*m_nAPointArrWidth+x] == 'E')
{
m_pAPointArr[y*m_nAPointArrWidth+x].type = APT_ENDPOINT;
m_pEndPoint = m_pAPointArr + y*m_nAPointArrWidth+x;
}else{
m_pAPointArr[y*m_nAPointArrWidth+x].type = APT_UNKNOWN;
}
}
}
return TRUE;
}
void CAStarBase::SetStartPoint( int x,int y )
{
if ( m_pStartPoint && m_pAPointArr[y*m_nAPointArrWidth+x].type!=APT_CLOSED )
{
m_pStartPoint->type = APT_OPENED;
// 设置新的值
m_pStartPoint = m_pAPointArr + y*m_nAPointArrWidth+x;
m_pStartPoint->type = APT_STARTPOINT;
m_pCurPoint = m_pStartPoint;
}
}
void CAStarBase::SetEndPoint( int x,int y )
{
if ( m_pStartPoint && m_pAPointArr[y*m_nAPointArrWidth+x].type!=APT_CLOSED )
{
m_pStartPoint->type = APT_OPENED;
// 设置新的值
m_pStartPoint = m_pAPointArr + y*m_nAPointArrWidth+x;
m_pStartPoint->type = APT_ENDPOINT;
}
}
void CAStarBase::SetCurrent( int x,int y )
{
// if ( m_pAPointArr[y*m_nAPointArrWidth+x].type==APT_OPENED )
{
m_pCurPoint = m_pAPointArr+y*m_nAPointArrWidth+x;
}
}
void CAStarBase::SetOpened( int x,int y )
{
if ( m_pAPointArr[y*m_nAPointArrWidth+x].type!=APT_OPENED )
{
m_pAPointArr[y*m_nAPointArrWidth+x].type = APT_OPENED;
}
}
void CAStarBase::SetClosed( int x,int y )
{
if ( m_pAPointArr[y*m_nAPointArrWidth+x].type!=APT_CLOSED )
{
m_pAPointArr[y*m_nAPointArrWidth+x].type = APT_CLOSED;
}
}
void CAStarBase::PrintCharArr()
{
if ( m_pOldArr )
{
for ( int y=0; y<m_nAPointArrHeight;y++)
{
for ( int x=0;x<m_nAPointArrWidth;x++)
{
printf("%c ",m_pOldArr[x+m_nAPointArrWidth*y]);
}
printf("\r\n");
}
printf("\r\n");
}
}
PAPoint CAStarBase::CalcNextPoint( PAPoint ptCalc )
{
if ( ptCalc == NULL )
{
ptCalc = m_pStartPoint;
}
int x = ptCalc->x;
int y = ptCalc->y;
int dx = m_pEndPoint->x;
int dy = m_pEndPoint->y;
int xmin = x,ymin = y,vmin = 0; // 最优步骤的坐标和值
// 判断是否已经到了最终的位置
if ( (x==dx && abs(y-dy)==1) || (y==dy && abs(x-dx)==1) )
{
return m_pEndPoint;
}
// 上
if ( m_pAPointArr[(x+0)+m_nAPointArrWidth*(y-1)].type == APT_OPENED && y>0)
{
m_pAPointArr[(x+0)+m_nAPointArrWidth*(y-1)].g = 10;
m_pAPointArr[(x+0)+m_nAPointArrWidth*(y-1)].h =
10*(abs(x - dx) + abs(y-1 - dy));
m_pAPointArr[(x+0)+m_nAPointArrWidth*(y-1)].f =
m_pAPointArr[(x+0)+m_nAPointArrWidth*(y-1)].g + m_pAPointArr[(x+0)+m_nAPointArrWidth*(y-1)].h;
if ( vmin==0 )
{
xmin = x;
ymin = y-1;
vmin = m_pAPointArr[(x+0)+m_nAPointArrWidth*(y-1)].f;
}else{
if ( vmin > m_pAPointArr[(x+0)+m_nAPointArrWidth*(y-1)].f )
{
xmin = x;
ymin = y-1;
vmin = m_pAPointArr[(x+0)+m_nAPointArrWidth*(y-1)].f;
}
}
}
// 下
if ( m_pAPointArr[(x+0)+m_nAPointArrWidth*(y+1)].type == APT_OPENED && y<m_nAPointArrHeight)
{
m_pAPointArr[(x+0)+m_nAPointArrWidth*(y+1)].g = 10;
m_pAPointArr[(x+0)+m_nAPointArrWidth*(y+1)].h =
10*(abs(x - dx) + abs(y+1 - dy));
m_pAPointArr[(x+0)+m_nAPointArrWidth*(y+1)].f =
m_pAPointArr[(x+0)+m_nAPointArrWidth*(y+1)].g + m_pAPointArr[(x+0)+m_nAPointArrWidth*(y+1)].h;
if ( vmin==0 )
{
xmin = x;
ymin = y+1;
vmin = m_pAPointArr[(x+0)+m_nAPointArrWidth*(y+1)].f;
}else{
if ( vmin > m_pAPointArr[(x+0)+m_nAPointArrWidth*(y+1)].f )
{
xmin = x;
ymin = y+1;
vmin = m_pAPointArr[(x+0)+m_nAPointArrWidth*(y+1)].f;
}
}
}
// 左
if ( m_pAPointArr[(x-1)+m_nAPointArrWidth*y].type == APT_OPENED && x>0)
{
m_pAPointArr[(x-1)+m_nAPointArrWidth*y].g = 10;
m_pAPointArr[(x-1)+m_nAPointArrWidth*y].h =
10*(abs(x-1 - dx) + abs(y - dy));
m_pAPointArr[(x-1)+m_nAPointArrWidth*y].f =
m_pAPointArr[(x-1)+m_nAPointArrWidth*y].g + m_pAPointArr[(x-1)+m_nAPointArrWidth*y].h;
if ( vmin==0 )
{
xmin = x-1;
ymin = y;
vmin = m_pAPointArr[(x-1)+m_nAPointArrWidth*y].f;
}else{
if ( vmin > m_pAPointArr[(x-1)+m_nAPointArrWidth*y].f )
{
xmin = x-1;
ymin = y;
vmin = m_pAPointArr[(x-1)+m_nAPointArrWidth*y].f;
}
}
}
// 右
if ( m_pAPointArr[(x+1)+m_nAPointArrWidth*y].type == APT_OPENED && x<m_nAPointArrWidth)
{
m_pAPointArr[(x+1)+m_nAPointArrWidth*y].g = 10;
m_pAPointArr[(x+1)+m_nAPointArrWidth*y].h =
10*(abs(x+1 - dx) + abs(y - dy));
m_pAPointArr[(x+1)+m_nAPointArrWidth*y].f =
m_pAPointArr[(x+1)+m_nAPointArrWidth*y].g + m_pAPointArr[(x+1)+m_nAPointArrWidth*y].h;
if ( vmin==0 )
{
xmin = x+1;
ymin = y;
vmin = m_pAPointArr[(x+1)+m_nAPointArrWidth*y].f;
}else{
if ( vmin > m_pAPointArr[(x+1)+m_nAPointArrWidth*y].f )
{
xmin = x+1;
ymin = y;
vmin = m_pAPointArr[(x+1)+m_nAPointArrWidth*y].f;
}
}
}
// 如果有最优点则迭代,则否就返回NULL
if ( vmin )
{
SetCurrent(xmin,ymin);
SetClosed(xmin,ymin);
*(m_pOldArr+xmin+m_nAPointArrWidth*ymin) = '-';
PrintCharArr();
PAPoint pApoint = CalcNextPoint(m_pCurPoint);
if ( pApoint == NULL )
{
SetCurrent(x,y);
SetClosed(xmin,ymin);
*(m_pOldArr+xmin+m_nAPointArrWidth*ymin) = '0';
return CalcNextPoint(m_pCurPoint);
}
return pApoint;
}else{
return NULL;
}
}
</span>
看看上面的代码,算法的伪程序如下:
A_Star_Search()
{
Open = [起始节点];
Closed = [];
while ( Open表非空 )
{
从Open中取得一个节点X, 并从OPEN表中删除.
if (X是目标节点)
{
求得路径PATH;
返回路径PATH;
}
for (每一个X的子节点Y)
{
if( Y不在OPEN表和CLOSE表中 )
{
求Y的估价值;
并将Y插入OPEN表中; //还没有排序
}
else if( Y在OPEN表中 )
{
if( Y的估价值小于OPEN表的估价值 )
更新OPEN表中的估价值;
}
else //Y在CLOSE表中
{
if( Y的估价值小于CLOSE表的估价值 )
{
更新CLOSE表中的估价值;
从CLOSE表中移出节点, 并放入OPEN表中;
}
}
将X节点插入CLOSE表中;
按照估价值将OPEN表中的节点排序;
} //end for
} //end while
} //end func
上下左右注释后面的if代码片,就是估值过程。
A*算法也有变种,Beam Search(集束搜索)就是其中一种,只是BS中的open集有大小限制,不像A*保存所有需要检查的节点。