一、算法简介
A* (A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是许多其他问题的常用启发式算法。是最有效的直接搜索算法,之后涌现了很多预处理算法(如ALT,CH,HL等等),在线查询效率是A*算法的数千甚至上万倍。
公式表示为: f(n)=g(n)+h(n),
其中, f(n) 是从初始状态经由状态n到目标状态的代价估计,
g(n) 是在状态空间中从初始状态到状态n的实际代价,
h(n) 是从状态n到目标状态的最佳路径的估计代价。
(对于路径搜索问题,状态就是图中的节点,代价就是距离)
h(n)的选取
保证找到最短路径(最优解的)条件,关键在于估价函数f(n)的选取(或者说h(n)的选取)。
我们以d(n)表达状态n到目标状态的距离,那么h(n)的选取大致有如下三种情况:
如果h(n)< d(n)到目标状态的实际距离,这种情况下,搜索的点数多,搜索范围大,效率低。但能得到最优解。
如果h(n)=d(n),即距离估计h(n)等于最短距离,那么搜索将严格沿着最短路径进行, 此时的搜索效率是最高的。
如果 h(n)>d(n),搜索的点数少,搜索范围小,效率高,但不能保证得到最优解。
二、示例演示
三、程序编写分析
A*与Dijkstra算法的区别就是加了一个h函数,在OPEN表中取最小值时就不是取离起始点最近的值,而是取离起始点距离+h函数计算得到距离的最小值代表的节点,这样的话就可以朝着目标点进行搜索。
建立两个列表,然后建立一个struct节点,编写h函数,进行判断。
把起始格添加到 "开启列表"
do
{
寻找开启列表中F值最低的格子, 我们称它为当前格.
把它切换到关闭列表.
对当前格相邻的8格中的每一个
if (它不可通过 || 已经在 "关闭列表" 中)
{
什么也不做.
}
if (它不在开启列表中)
{
把它添加进 "开启列表", 把当前格作为这一格的父节点, 计算这一格的 FGH
if (它已经在开启列表中)
{
if (用G值为参考检查新的路径是否更好, 更低的G值意味着更好的路径)
{
把这一格的父节点改成当前格, 并且重新计算这一格的 GF 值.
}
} while( 目标格已经在 "开启列表", 这时候路径被找到)
如果开启列表已经空了, 说明路径不存在.
最后从目标格开始, 沿着每一格的父节点移动直到回到起始格, 这就是路径.
四、程序编写
#pragma once
#include<iostream>
#include<vector>
#include<algorithm>
#include<list>
#include<math.h>
using namespace std;
//创建地图
vector<vector<int>>maze = {
{ 0, 1, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
};
//创建节点的通用形式
struct Point{
int x, y;
float F, G, H;
Point *parent;
Point(int _x, int _y) :x(_x), y(_y), F(0), G(0), H(0), parent(NULL){}
};
Point startPoint(0, 0), endPoint(1, 4);
//实现升排列的谓词
bool cmp(Point *a, Point* b)
{
return (a->F < b->F);
}
//是否在链表中
bool isInList(Point *p, list<Point*>list){
for (auto p1 : list){
if (p->x == p1->x && p->y == p1->y)
return true;
}
return false;
}
//得到估价值F/G/H
void getFGH(Point &middle){
if (middle.parent == NULL){
middle.G = 0;
}
else middle.G = middle.parent->G + sqrt((double)(middle.parent->x - middle.x)*(middle.parent->x - middle.x) + (double)(middle.parent->y - middle.y)*(middle.parent->y - middle.y));
middle.H = sqrt((double)(endPoint.x - middle.x)*(endPoint.x - middle.x) + (double)(endPoint.y - middle.y)*(endPoint.y - middle.y));
middle.F = middle.G + middle.H;
}
//得到两点距离值,这是一个简单的函数
float getdis(Point &a, Point &b){ return (double)sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y)); }
//进行路径的寻找
int Astar(){
list<Point*>openlist;
list<Point*>closelist;
//将第一个点放入openlist
getFGH(startPoint);
openlist.push_back(&startPoint);
//创建一个指针类型的变量,作为中介接收取出的最小值点
Point *temp = new Point(0, 0);
while (!openlist.empty()){
//找出最小F的点,list中自带有排序函数就不用algorithm
openlist.sort(cmp);
temp = openlist.front();
openlist.pop_front();
closelist.push_back(temp);
for (int i = temp->x - 1; i <= temp->x + 1; i++){
if (i<0 || i>5) continue;
for (int j = temp->y - 1; j <= temp->y + 1; j++){
Point* temp1 = new Point(i, j);
//超出范围、不可通过、在关闭列表
if (j<0 || j>5 || maze[i][j] == 1 || isInList(temp1, closelist)) continue;
//不在开始列表,则应该添加进开始列表,同时要以此节点为父节点
if (!isInList(temp1, openlist)){
temp1->parent = temp;
getFGH(*temp1);
openlist.push_back(temp1);
}
//在开始列表,判断G值是否会更短,会的话就要更新(这一步同Dijkstra)
if (isInList(temp1, openlist)){
getFGH(*temp);
if (temp1->G > temp->G + getdis(*temp, *temp1)){
temp1->G = temp->G + getdis(*temp, *temp1);
temp1->F = temp1->G + temp1->H;
temp1->parent = temp;
}
}
//是否终点已经放入开始列表
if (isInList(&endPoint, openlist)) {
endPoint = *temp1;
return 0;
}
}
}
}
}
void main(){
Astar();
//将各个节点依照前插的方式进行存储,那么输出就可以顺序输出了
list<Point>path;
path.push_front(endPoint);
while (endPoint.parent != NULL){
path.push_front(*(endPoint.parent));
endPoint = (*(endPoint.parent));
}
for (auto p : path){
cout << "( " << p.x << ", " << p.y << ") " << endl;
}
system("pause");
}