A*算法—第K短路

A*算法—第K短路

A* 算法(這裏的* 英文就讀作star),是一種啓發式搜索的方法,它離我們並不遙遠,常用的BFS就是A*算法的一種特例。

啓發式搜索:

DFS與BFS都屬於非啓發式搜索,又稱盲目型搜索,它們最大的不同就是啓發式搜索的選擇不是盲目的,可以通過一個啓發函數進行選擇。
現在看一下下面的兩張圖,就可以很形象的理解了:

假如正常的搜索方式,我們會不斷移動,直至遇到障礙物,顯然這種方法是很愚蠢的,但是正常的方法的確是這樣進行的,那麼我們就希望擴展一個運動算法,用於對付上圖所示的障礙物。或者避免製造凹形障礙,或者把凹形出口標識爲危險的(只有當目的地在裏面時才進去):

A*算法:

爲啓發式算法中很重要的一種,被廣泛應用在最優路徑求解和一些策略設計的問題中。而A*算法最爲核心的部分,就在於它的一個估值函數的設計上:
f(n)=g(n)+h(n)
其中f(n)是每個可能試探點的估值,它有兩部分組成:一部分爲g(n),它表示從起始搜索點到當前點的代價(通常用某結點在搜索樹中的深度來表示)。另一部分,即h(n),它表示啓發式搜索中最爲重要的一部分,即當前結點到目標結點的估值,h(n)設計的好壞,直接影響着具有此種啓發式函數的啓發式算法的是否能稱爲A*算法。

一種具有f(n)=g(n)+h(n)策略的啓發式算法能成爲A算法的充分條件是:

  1. 搜索樹上存在着從起始點到終了點的最優路徑。
  2. 問題域是有限的。
  3. 所有結點的子結點的搜索代價值>0。
  4. h(n) <= h * (n) (h*(n)爲實際問題的代價值)。

一般的搜索前三條都可以滿足,而第四點就要視情況而定了。
如果上面的概念不能很好理解也沒關係,下面來看一道A*算法的經典題目,求第K短路【POJ2449】—— [ 題目鏈接 ]:

Description

“Good man never makes girls wait or breaks an appointment!” said the mandarin duck father. Softly touching his little ducks’ head, he told them a story.

“Prince Remmarguts lives in his kingdom UDF – United Delta of Freedom. One day their neighboring country sent them Princess Uyuw on a diplomatic mission.”

“Erenow, the princess sent Remmarguts a letter, informing him that she would come to the hall and hold commercial talks with UDF if and only if the prince go and meet her via the K-th shortest path. (in fact, Uyuw does not want to come at all)”

Being interested in the trade development and such a lovely girl, Prince Remmarguts really became enamored. He needs you - the prime minister’s help!

DETAILS: UDF’s capital consists of N stations. The hall is numbered S, while the station numbered T denotes prince’ current place. M muddy directed sideways connect some of the stations. Remmarguts’ path to welcome the princess might include the same station twice or more than twice, even it is the station with number S or T. Different paths with same length will be considered disparate.

Input

The first line contains two integer numbers N and M (1 <= N <= 1000, 0 <= M <= 100000). Stations are numbered from 1 to N. Each of the following M lines contains three integer numbers A, B and T (1 <= A, B <= N, 1 <= T <= 100). It shows that there is a directed sideway from A-th station to B-th station with time T.
The last line consists of three integer numbers S, T and K (1 <= S, T <= N, 1 <= K <= 1000).

Output

A single line consisting of a single integer number: the length (time required) to welcome Princess Uyuw using the K-th shortest path. If K-th shortest path does not exist, you should output “-1” (without quotes) instead.

Sample Input

2 2
1 2 5
2 1 4
1 2 2

Sample Output

14


在這道題目中,要求很明確,就是需要你求從s到t的第K短路

第k短路:

K短路的定義:假設從1出發,有M條長度不同的路徑可以到達點N,則K短路就是這M條路徑中第K小的路徑長度。
以上所述,設f[n]爲最終所求,則f(n)=g(n)+h(n);h(n)就是我們所說的‘啓發式函數’,表示爲重點t到其餘一點p的路徑長度,g(n)表示g當前從s到p所走的路徑的長度。

估價函數=當前值+當前位置到終點的距離

Solution:

(1)將有向圖的所有邊反向,以原終點t爲源點,求解t到所有點的最短距離;
(2)新建一個優先隊列,將源點s加入到隊列中;
(3)從優先級隊列中彈出f(p)最小的點p,如果點p就是t,則計算t出隊的次數;
如果當前爲t的第k次出隊,則當前路徑的長度就是s到t的第k短路的長度,算法結束;
否則遍歷與p相連的所有的邊,將擴展出的到p的鄰接點信息加入到優先級隊列;

Code:

#include<string.h>
#include<algorithm>
#include<queue> 
using namespace std;
#define INF 0xffffff
#define MAXN 100010
struct node
{
    int to;
    int val;
    int next;
};
struct node2
{
    int to;
    int g,f;
    bool operator<(const node2 &r ) const  
    {  
        if(r.f==f)  
            return r.g<g;  
        return r.f<f;  
    }   
};
node edge[MAXN],edge2[MAXN];
int n,m,s,t,k,cnt,cnt2,ans;
int dis[1010],visit[1010],head[1010],head2[1010];
void init()
{
    memset(head,-1,sizeof(head));
    memset(head2,-1,sizeof(head2));
    cnt=cnt2=1;
}
void addedge(int from,int to,int val)
{
    edge[cnt].to=to;
    edge[cnt].val=val;
    edge[cnt].next=head[from];
    head[from]=cnt++;
}
void addedge2(int from,int to,int val)
{
    edge2[cnt2].to=to;
    edge2[cnt2].val=val;
    edge2[cnt2].next=head2[from];
    head2[from]=cnt2++;
}
bool spfa(int s,int n,int head[],node edge[],int dist[])  
{  
    queue<int>Q1;  
    int inq[1010];  
    for(int i=0;i<=n;i++)  
    {  
        dis[i]=INF;  
        inq[i]=0;  
    }  
    dis[s]=0;  
    Q1.push(s);  
    inq[s]++;  
    while(!Q1.empty())  
    {  
        int q=Q1.front();  
        Q1.pop();  
        inq[q]--;  
        if(inq[q]>n)
            return false;  
        int k=head[q];  
        while(k>=0)  
        {  
            if(dist[edge[k].to]>dist[q]+edge[k].val)  
            {  
                dist[edge[k].to]=edge[k].val+dist[q];  
                if(!inq[edge[k].to])  
                {  
                    inq[edge[k].to]++;  
                    Q1.push(edge[k].to);  
                }  
            }  
            k=edge[k].next;  
        }  
    }  
    return true;  
}
int A_star(int s,int t,int n,int k,int head[],node edge[],int dist[]) 
{  
    node2 e,ne;  
    int cnt=0;  
    priority_queue<node2>Q;  
    if(s==t)
        k++;  
    if(dis[s]==INF)  
        return -1;  
    e.to=s;  
    e.g=0;  
    e.f=e.g+dis[e.to];  
    Q.push(e);  

    while(!Q.empty())  
    {  
        e=Q.top();  
        Q.pop();  
        if(e.to==t)//找到一條最短路徑  
        {  
            cnt++;  
        }  
        if(cnt==k)//找到k短路  
        {  
            return e.g;  
        }  
        for(int i=head[e.to]; i!=-1; i=edge[i].next)  
        {  
            ne.to=edge[i].to;  
            ne.g=e.g+edge[i].val;  
            ne.f=ne.g+dis[ne.to];  
            Q.push(ne);  
        }  
    }  
    return -1;  
}  
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i=1;i<=m;i++)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            addedge(a,b,c);
            addedge2(b,a,c);
        }
        scanf("%d%d%d",&s,&t,&k);
        spfa(t,n,head2,edge2,dis);
        ans=A_star(s,t,n,k,head,edge,dis);
        printf("%d\n",ans);
    }

    return 0;
} 

A*對啓發式函數的使用

·h(n)越小,運行的越慢
·如果h(n)=0,則只有g(n),此時的A*就變成了Dijkstra算法。
·如果h(n)比g(n)大很多,則A*就變成了BFS

所以我們得到一個很有趣的情況,那就是我們可以決定我們想要從A*中獲得什麼。理想情況下(注:原文爲At exactly the right point),我們想最快地得到最短路徑。如果我們的目標太低,我們仍會得到最短路徑,不過速度變慢了;如果我們的目標太高,那我們就放棄了最短路徑,但A*運行得更快。 —— [ 參考 ]

注:在學術上,如果啓發式函數值是對實際代價的低估,A*算法被稱爲簡單的A算法(原文爲simply A)。然而,我繼續稱之爲A*,因爲在實現上是一樣的,並且在遊戲編程領域並不區別A和A*。

ADD:

  • A*算法在理論上是時間最優的,但是也有缺點:它的空間增長是指數級別的。
  • IDA*算法:這種算法被稱爲迭代加深A*算法,可以有效的解決A*空間增長帶來的問題,甚至可以不用到優先級隊列。如果要知道詳細:google一下。
  • 可以認爲BFS是“最爛的”A*算法
  • 啓發式搜索中,對位置的估價是十分重要的。採用了不同的估價可以有不同的效果

比較BFS與A*算法:

Sumarry

我們可以看出:A*算法最爲核心的過程,就在每次選擇下一個當前搜索點時,是從所有已探知的但未搜索過點中(可能是不同層,亦可不在同一條支路上),選取f值最小的結點進行展開。而所有“已探知的但未搜索過點”可以通過一個按f值升序的隊列(即優先隊列)進行排列。這樣,在整體的搜索過程中,只要按照類似廣度優先的算法框架,從優先隊列中彈出隊首元素(f值),對其可能子結點計算g、h和f值,直到優先隊列爲空(無解)或找到終止點爲止。
或者流程圖:

Created with Raphaël 2.1.0排列已知點優先隊列選f最小的未知點結束yesno
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章