POJ2449A*求K短路

暑期培訓,胖子交給我一個艱鉅的任務,就是講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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章