A*算法
Posted rotepad
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了A*算法相关的知识,希望对你有一定的参考价值。
网上的要么是解释一大堆,枯燥乏味,要么代码恶心难以理解,以下图片生动形象的解释了A*算法,后面的解释也是通俗易懂,可以带着解释去看代码,看的很容易理解,学算法就是这样,找到一篇好的博客,理解学习都很迅速。
先了解A*算法:最容易理解的A*算法解释
现在来解决A*求K短路问题
在一个有权图中,从起点到终点最短的路径成为最短路,第2短的路成为次短路,第3短的路成为第3短路,依此类推,第k短的路成为第k短路。那么,第k短路怎么求呢?
对于第k短路,可以想到的一个比较朴素的算法就是广度优先搜索,使用优先队列从源点s进行广搜,当第k次搜索到终点t时,所的长度即所求但是这种方法在运行过程中会产生特别多的状态,当图比较简单、k比较小时,可以一试,但是当k较大或者图中点数较多时,会面临爆栈的危险。目前使用比较多的算法是单源最短路配合A*。A*是搜索中比较高级的方式,A*算法结合了启发式方法(这种方法通过充分利用图给出的信息来动态的作出决定而使搜索次数大大降低)和形式化方法(这种方法不利用图给出的信息,而仅通过数学的形式分析,如Dijkstra算法)。它通过一个估价函数f(h)来估计图中的当前点p到终点的距离,并由此决定它的搜索方向,当这条路径失败时,它会尝试其他路径。对于A*,估价函数=当前值+当前位置到终点的距离,即f(p)=g(p)+h(p),每次扩展估价函数值最小的一个。对于第k短路算法来说,g(p)为从源点s到当前点p所走的路径长度,h(p)为从当前点p到终点t的最短路,因此f(p)的意义就是从s按照当前路径经过p点后到达t的总距离。也就是每次扩展都是有方向的,这样无论对提高出解的速度还是降低扩展的状态数目都是有好处的。为了加快计算,h(p)需要在搜索之前进行预处理,只要将原图的所有边反向,再从终点t做一次单源最短路即可得到h(p)。单源最短路求法有Dijkstra,Bellman-Ford,SPFA等。
具体步奏:
这里我们使用链式前向星来存储如图,由于需要预处理所有点到终点的最短路,就需要将图G中所有边反向得到图G‘,再从终点t做一次单源最短路,所以实际上就是两张图。
(1)将有向图的所有边反向(无向图可以省略此步),以原图终点t为源点做一次单源最短路,结果记入数组dis[i]中,dis[i]即为原图中点i到点t的最短距离。这里的dis[i]即上述的h(p);
(2)新建一个优先队列,将源点s加入到队列中;
(3)从优先队列中弹出f(p)最小的点p(这里如果存在f(p)相等的点,则弹出g(p)最小的点),如果点p就是终点t,则计算t出队列的次数,如果当前为t的第k次出队,则当前路径长度就是s到t的第k短路,算法结束;否则遍历与p相连的所有的边,将扩展出的到p的邻接点信息加入到优先队列。
值得注意的是,当s==t时需要计算(k+1)短路,因为s到t这条距离为0的路不能算在这k短路中,这时只需将k自增1后再求第k短路即可。
代码:(以POJ 2449 Remmarguts‘ Date 为例)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<stack>
#include<cmath>
#define M 100005
#define N 1005
#define INF 0x3f3f3f3f
using namespace std;
int n,m,c1,c,st,en,k;
int head[N],head1[N],dis[N];
//dis为从当前点p到终点t的最短路
bool vis[N];
struct ljh
{
int next,to,w;
}e[M],a[M];
struct xqy
{
int pos,g,f;//g为从源点s到当前点p所走的路径长度,f为A*算法(也就是类似启发式搜索)的F值
xqy(int pos=0,int g=0,int f=0):pos(pos),g(g),f(f){}
bool operator < (const xqy&a)const
{
if(a.f==f)return a.g<g;
return a.f<f;
}
};
inline void add(int x,int y,int z)
{
e[c].next=head[x];
e[c].w=z;
e[c].to=y;
head[x]=c++;
}
inline void add1(int x,int y,int z)
{
a[c1].next=head1[x];
a[c1].w=z;
a[c1].to=y;
head1[x]=c1++;
}
void SPFA(int x)
{
queue<int>q;
for(int i=1;i<=n;i++)dis[i]=INF;
memset(vis,0,sizeof(vis));
dis[x]=0;
q.push(x);
vis[x]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
vis[now]=0;
// cout<<now<<endl;
for(int i=head1[now];i!=-1;i=a[i].next)
{
int nex=a[i].to;
// cout<<nex<<endl;
if(dis[nex]>dis[now]+a[i].w)
{
dis[nex]=dis[now]+a[i].w;
if(!vis[nex])
{
q.push(nex);
vis[nex]=1;
}
}
}
}
return ;
}
int A_star(int st,int en,int k)
{
int num=0;
if(st==en)k++;
if(dis[st]==INF)return -1;
priority_queue<xqy>q;
q.push(xqy(st,0,dis[st]));
while(!q.empty())
{
xqy now=q.top();
q.pop();
if(now.pos==en)num++;
if(num==k)return now.g;//相当于走到en点所走的路程
for(int i=head[now.pos];i!=-1;i=e[i].next)
{
xqy nex;
nex.pos=e[i].to;
nex.g=now.g+e[i].w;
nex.f=(now.g+e[i].w)+dis[e[i].to];
q.push(nex);
}
}
return -1;
}
int main()
{
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
while(~scanf("%d%d",&n,&m))
{
c=0;
c1=0;
memset(head,-1,sizeof(head));
memset(head1,-1,sizeof(head1));
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add1(y,x,z);//反向建图
}
scanf("%d%d%d",&st,&en,&k);
SPFA(en);
// for(int i=1;i<=n;i++)cout<<dis[i]<<" ";cout<<endl;
printf("%d
",A_star(st,en,k));
}
}
以上是关于A*算法的主要内容,如果未能解决你的问题,请参考以下文章