A*算法的認識與求第K短路模板


現在來了解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 ( 第 k 短路 && A*算法 )

題意 : 給出一個有向圖、求起點 s 到終點 t 的第 k 短路、不存在則輸出 -1

#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
const int INF  = 0x3f3f3f3f;
const int maxn = 1024;
const int maxm = 100008;
struct EDGE{ int v, nxt, w; };
struct NODE{
    int pos, Cost, F;
    bool operator < (const NODE & rhs) const {//重載的時候注意符號
        if(this->F == rhs.F) return this->Cost > rhs.Cost;
        return this->F > rhs.F;
    };
};

EDGE Edge[maxm];
EDGE REdge[maxm];
int Head[maxn], RHead[maxn];
int cnt, Rcnt;
int N;
void init()
{
    memset(Head, -1, sizeof(Head));
    memset(RHead, -1, sizeof(RHead));
    cnt = Rcnt = 0;
}

void AddEdge(int from, int to, int weight)
{
    Edge[cnt].w = weight;
    Edge[cnt].v = to;
    Edge[cnt].nxt = Head[from];
    Head[from] = cnt++;
}

void AddREdge(int from, int to, int weight)
{
    REdge[Rcnt].w = weight;
    REdge[Rcnt].v = to;
    REdge[Rcnt].nxt = RHead[from];
    RHead[from] = Rcnt++;
}

int vis[maxn];
int H[maxn];
void SPFA(int st)
{
    queue<int> que;
    memset(H, INF, sizeof(H));
    memset(vis, 0, sizeof(vis));
    H[st] = 0;
    que.push(st);
    while(!que.empty()){
        int cur = que.front(); que.pop();
        vis[cur] = 0;
        for(int i=RHead[cur]; i!=-1; i=REdge[i].nxt) {
            int v = REdge[i].v;
            if(H[v] > H[cur] + REdge[i].w) {
                H[v] = H[cur] + REdge[i].w;
                if(!vis[v]) {
                    vis[v] = 1;
                    que.push(v);
                }
            }
        }
    }
}

int A_Star(int s, int t, int k)
{
    if(s == t) k++;
    if(H[s]==INF) return -1;
    priority_queue<NODE> que;
    NODE cur, into;
    cur.pos = s;
    cur.Cost = 0;
    cur.F = H[s];
    que.push(cur);
    int CNT = 0;
    while(!que.empty()){
        cur = que.top();
        que.pop();
        if(cur.pos == t) CNT++;
        if(CNT == k) return cur.Cost;
        for(int i=Head[cur.pos]; i!=-1; i=Edge[i].nxt){
            into.Cost = cur.Cost+Edge[i].w;
            into.F = cur.Cost+Edge[i].w+H[Edge[i].v];
            into.pos = Edge[i].v;
            que.push(into);
        }
    }return -1;
}


int main(void)
{
    int M, K, S, des;
    while(~scanf("%d %d", &N, &M)){
        init();
        int from, to, weight;
        while(M--){
            scanf("%d %d %d",&from, &to, &weight);
             AddEdge(from, to, weight);
            AddREdge(to, from, weight);//建反向邊
        }
        scanf("%d %d %d", &S, &des, &K);
        SPFA(des);//求其他點到終點的最短路,作為H值
        printf("%d\n", A_Star(S,des,K));
    }
    return 0;
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM