暑期培訓,胖子交給我一個艱鉅的任務,就是講A*,自己弄懂還是蠻簡單,但是要講出去談何容易,無奈我只能再拿起來重新閱讀。
一、現實舉例
1.我們玩遊戲時,經常點擊一下鼠標,遊戲角色就會自動按照最短路徑走向目的地,這需要搜索最短路徑,這都是遊戲程序幫我們完成了。遊戲程序是如何做到高效完成的呢?
2.“衆裏尋他千百度,暮然回首,那人卻在燈火闌珊處”,要是我們一開始便知道伊人會在燈火闌珊處,便開始尋找燈火的地方,燈火闌珊的地方,最後便很容易找到那個朝思暮想的人了,又何苦追尋千百度,讓BAIDU出名呢?
二、欲練此功,必先了解:
1.BFS寬度優先搜索以及Djisktra。寬度優先搜索就是每次擴展距離自己最近的,尚未被擴展的節點進行擴展,知道最後找到目標。而A*的一部分正好和BFS相像;
2.啓發式搜索:講到A*,就不得不提啓發式搜索。啓發式搜索就是對當前的搜索狀態進行評估,我們當然每次都想選取最好的來進行擴展,這樣才能節省時間。例如導語,我們每次尋找的地點得看它到燈火闌珊處的距離,若越來越遠,顯然是不行的。
3.估價函數:從當前節點移動到目標節點的預估費用;這個估計就是啓發式的。
4.選取最小估價:這裏我們要用到優先隊列
優先隊列:我們已經在前幾天的學習中對優先隊列有了詳細的認識,而在很多用A*算法解決的問題中,優先隊列是必不可少的。在這裏再來複習一下優先隊列。
#include<queue>
struct node{
int a;
bool operator < (node b) const{return b.a<a;}
};
5所謂的最小估價,其實就是一種猜測,也許是生活經驗,又或許是目測或通過他人的成果等等。但這個最小估價可能不是實際值,因爲路上可能出現路坑或者河流等等阻礙着前進。
我們來看一個圖加深理解:
我們優先搜索節點N還是節點M?
按照Dijstra會搜索節點M,但我們搜索節點N。 因爲節點N距離終點的估價值明顯幣節點M的短。
三、A*公式提煉
f[n]=g[n]+h[n];
f[n]爲估價函數的值;
g[n]爲從起始狀態到當前狀態的耗散值
h[n]爲從當前狀態到目標狀態的啓發值
一般的BFS或djisktra的h[n]爲0,因爲他們總是隻貪當前最優,而不管後繼狀態是否對其有影響。
四、A*的關鍵
好了,從上面的思考中我們已經可以推出A*的關鍵就是構造h[n],只要h[n]構造得好,我們就可以既快又準確的找到答案。而h[n]又是到目標狀態的啓發值,所以大多數情況下我們可以先從目標狀態出發先找到他的h[n].
五、基本步驟(此處轉自某論文,名稱地址我已記不得了,但是這只是模版,實際題目中還有一些改動的地方)
1,把起始狀態添加到開啓列表。
2,重複如下的工作:
a) 尋找開啓列表中F值最低的狀態。我們稱它爲當狀態(這裏我們有優先隊列來維護)。
b) 把它切換到關閉列表。
c) 對相鄰的狀態中的每一個?
* 如果它不可通過或者已經在關閉列表中,略過它。反之如下。
* 如果它不在開啓列表中,把它添加進去。把當前格作爲這一格的父節點。記錄這一狀態的F,G,和H值。
如果它已經在開啓列表中,用G值爲參考檢查新的路徑是否更好。更低的G值意味着更好的路徑。如果是這樣,就把這一狀態的父節點改成當前狀態,並且重新計算這一格的G和F值。如果你保持你的開啓列表按F值排序,改變之後你可能需要重新對開啓列表排序。
d) 停止,當你
* 把目標格添加進了關閉列表,這時候路徑被找到,或者
* 沒有找到目標狀態,開啓列表已經空了。這時候,路徑不存在。
3.保存路徑。從目標狀態開始,沿着每一格的父節點移動直到回到起始狀態。這就是你的路徑。
#include<iostream>
#include<queue>
#define MAXN 1001
using namespace std;
struct node{
int g,h,p;
bool operator < (node a) const{
return a.g+a.h<g+h;
}
};
struct Q{
int u,v,w,next;
}line1[MAXN*100],line2[MAXN*100];
int link1[MAXN],link2[MAXN],h[MAXN],n,m,s,e,k,i;
bool used[MAXN];
priority_queue<node> myqueue;
void djikstra(){
int i,k,p;
memset(used,0,sizeof(used));
memset(h,0x7F,sizeof(h));//h函數初始值爲最大
h[e]=0;
for (p=1;p<=n;p++){
k=0;
for (i=1;i<=n;i++)
if (!used[i] && (!k||h[i]<h[k])) k=i;
used[k]=true;
k=link2[k];
while (k){
if (h[line2[k].v]>h[line2[k].u]+line2[k].w)
h[line2[k].v]=h[line2[k].u]+line2[k].w;
k=line2[k].next;
}
}
return ;
}
int Astar(){
int t,times[1001];
node g,temp;
while (!myqueue.empty()) myqueue.pop();
memset(times,0,sizeof(times));
g.p=s;g.g=0,g.h=h[s];
myqueue.push(g);
while (!myqueue.empty()){
g=myqueue.top();//每次取估價函數值最小的節點
myqueue.pop();
times[g.p]++;
if (times[g.p]==k && g.p==e) return g.g+g.h;
if (times[g.p]>k) continue;
t=link1[g.p];
while (t){//擴展,並把其加入優先隊列即openlist
temp.g=g.g+line1[t].w;
temp.h=h[line1[t].v];
temp.p=line1[t].v;
myqueue.push(temp);
t=line1[t].next;
}
}
return -1;
}
int main(){
cin>>n>>m;
memset(link1,0,sizeof(link1));
memset(link2,0,sizeof(link2));
for (i=1;i<=m;i++){
cin>>line1[i].u>>line1[i].v>>line1[i].w;
line1[i].next=link1[line1[i].u];//記錄與節點u有直接邊的節點
link1[line1[i].u]=i;
line2[i].u=line1[i].v;
line2[i].v=line1[i].u;
line2[i].w=line1[i].w;
line2[i].next=link2[line2[i].u];
link2[line2[i].u]=i;
}
cin>>s>>e>>k;
if (s==e) k++;
djikstra();
cout<<Astar()<<endl;
return 0;
}