之前一直想写一下 Astar 算法,因为最近考试耽误了些;
简述一下Astar的基本含义:基于BFS与 估价函数 的更优的 BFS;其与普通的BFS的主要区别是Astar具有 更优解优先搜索的顺序性,利用此性质可以解决一些看起来很 无厘头XD 的问题;
既然我们提到,BFS与Astar区别就在于 估价函数(一般称之为 f),那问题很显然就落到了如何设计一个好的 f 的问题;
首先明确一下f的性质并给出简略证明:
(定义 实际值(ans)为 g)(f=当前代价与未来可能代价)
1. f<=g,且f==g时BFS最易搜得最优解;
证明:1.很显然 估价函数正确的 估到了 正确答案,那么沿着这个路径一定是正解;(此时f一般为实时更新)
2.f<=g : Astar 是以 priorityqueue为基础实现的,则f越大(优)越先被更新,若f>g,则当前是按错误路径搜索
因为此时f所代表的路径非最优解(或正解),如若没有判断ans更新,则WA,若判断,那还不如BFS(因为此时 乱序搜索)
2. f愈趋近于0,越慢,且逐渐不具有 最优解在前性质,此时需要我们增加 判断ans更优的条件(就是BFS了);
对于 f 的个人理解:平衡多种 要求的 更优条件 以达到总体最优(并不是每一个最优);
讲完了基础知识,现在考虑一下k短路;
我们都知道,最短路一遍spfa就好;最长路同理;
那么次短路只需要再最短路基础上牺牲一条min(x边->y边(较大));
次次短路同理;
but,k短路和这个其实没有太大关系XD,不过这给了我们一个想法:
如果我们实时计算出刚刚的min,估计一下这个min可能是那几条边呢?
emm,算法来了:
很明显我们想知道,到x点花费了多长,和接下来我最小可以花多少,
这样我更新到end的时候就可以知道我花了多少而且是以一个较短的路线到达的;
即:f=到当前的代价(g)+到end的最短路(h);//其实g可以去掉,也能跑,这里是为了适应f的实际变化,以达到f<=g(更贴近)以加快效率;
所以,spfa反向跑一遍最短路得到h ,然后BFS+pq得到g;(ans) //and,,如果f写的极其好,第一次到end极为解(不仅仅是这道题)
下面贴代码,(emmm,,,不长,不打注释了哈XD)
#include
#include
#include
#include
using namespace std;
#define M 2000500
#define N 10000
#define INF 0xfffffff
int n,m,k;
int dis[N],vis[N];
int head[M],nxt[M],ver[M],val[M],cnt=0;
int head2[M],nxt2[M],ver2[M];
void add(int x,int y,int c){
nxt[++cnt]=head[x];
ver[cnt]=y;
val[cnt]=c;
head[x]=cnt;
nxt2[cnt]=head2[y];
ver2[cnt]=x;
head2[y]=cnt;
return ;
}
struct node{
int g,h,f;int to;
bool friend operator<(node a,node b){
return a.f>b.f;
}
};
struct node2{ //acer YY出来的 py 优化
int cost,num;
bool friend operator<(node2 x,node2 y){
return x.cost>y.cost;
}
};
void spfa(int s){
fill(dis+1,dis+1+n,INF);
memset(vis,0,sizeof(vis));
vis[s]=1;dis[s]=0;
node2 now,next;now.cost=0,now.num=s;
priority_queueq;q.push(now);
while(q.size()){
next=q.top();q.pop();vis[next.num]=0;
for(int i=head2[next.num];i;i=nxt2[i]){
if(dis[ver2[i]]>dis[next.num]+val[i]){
dis[ver2[i]]=dis[next.num]+val[i];
if(!vis[ver2[i]]){
vis[ver2[i]]=1;
now.cost=dis[ver2[i]];
now.num=ver2[i];
q.push(now);
}
}}}return ;
}
int Astar(int s,int end){
int tot=0;node e,t;
priority_queueQ;
if(s==end)k++;
if(dis[end]==INF)return -1;
e.to=s;e.g=0;e.h=dis[s];e.f=e.g+e.h;
Q.push(e);
while(Q.size()){
e=Q.top();Q.pop();
if(e.to==end)tot++;if(tot==k)return e.g;
for(int i=head[e.to];i;i=nxt[i]){
t.to=ver[i];
t.g=e.g+val[i];
t.h=dis[ver[i]];
t.f=t.g+t.h;
Q.push(t);
}
}return -1;
}
void clean(){
fill(head+1,head+1+n,0);
fill(head2+1,head2+1+n,0);
cnt=0;
}
int main(void){
int x,y,w,s,e;
while(scanf("%d%d",&n,&m)!=EOF){
clean();
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&w);
add(x,y,w);
}
scanf("%d%d%d",&s,&e,&k);
spfa(e);
int ans=Astar(s,e);
printf("%d\n",ans);
}
}
再说洛谷的P2349 金字塔
lara拿到了金字塔的地图,在她开箱子的时候碰到了机关
,,and放出了一阵使lara逃跑速度减半的毒烟。然后我们的lara开始跑圈;
题中给出的是双向边,好像保证联通来着;(不连通也没事,,lara会飞不成?)
lara想知道,在最坏情况下,花多久跑出去。(想了好久也不知道她知道这个干嘛。。)
so,,题目简化为,给定一张无向图与st与end ,求一条路,使得其路径上最大路径x2后,其花费最小;
很明显我们可以发现,题中同时维护了2个条件:1,总花费最小,2,路上max最小;
那这就很好办了,用f囊括一下,f=到当前的路径和+路上max+到end的最小值
然后仿着poj2249,Astar一边,完活~ ;)
#include
using namespace std;
#define M 100000
#define INF 0x3f3f3f3f
int head[M],nxt[M],ver[M],w[M],cnt=0;
void add(int x,int y,int c){nxt[++cnt]=head[x],head[x]=cnt,ver[cnt]=y,w[cnt]=c;return;}
int dis[M],vis[M];
struct node {
int f,g,h,num;
bool friend operator < (node a,node b){
return a.f>b.f;
}
};
void BFS(int s){
memset(vis,0,sizeof(vis));
memset(dis,INF,sizeof(dis));
dis[s]=0;vis[s]=1;
queueq;q.push(s);
while(q.size()){
int now=q.front();q.pop();vis[now]=0;
for(int i=head[now];i;i=nxt[i]){
if(dis[ver[i]]>dis[now]+w[i]){
dis[ver[i]]=dis[now]+w[i];
if(!vis[ver[i]]){
vis[ver[i]]=1;
q.push(ver[i]);
}
}
}
}
}
void Astar(int s,int n){
node e,t;
priority_queueQ;
e.h=0;e.g=0;e.f=e.h;e.num=s;
Q.push(e);
while(Q.size()){
e=Q.top();Q.pop();
if(e.num==n){
printf("%d\n",e.g+e.h);
return;
}
for(int i=head[e.num];i;i=nxt[i]){
t.h=e.h+w[i];
t.g=max(e.g,w[i]);
t.f=t.g+t.h+dis[ver[i]];
t.num=ver[i];
Q.push(t);
}
}
}
int main(void){
int n,m,x,y,c;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&c);
add(x,y,c);add(y,x,c);
}
BFS(n);
Astar(1,n);
}
and acer写了一个PY 的队列优化,在BFS,spfa这边很有用的,友链: PY优化XD