最短路經算法簡介(Dijkstra算法,A*算法,D*算法)


 據 Drew 所知最短路經算法現在重要的應用有計算機網絡路由算法,機器人探路,交通路線導航,人工智能,游戲設計等等。美國火星探測器核心的尋路算法就是采用的D*(D Star)算法。

最短路經計算分靜態最短路計算和動態最短路計算。

    靜態路徑最短路徑算法是外界環境不變,計算最短路徑。主要有Dijkstra算法,A*(A Star)算法。 

    動態路徑最短路是外界環境不斷發生變化,即不能計算預測的情況下計算最短路。如在游戲中敵人或障礙物不斷移動的情況下。典型的有D*算法

 Dijkstra算法求最短路徑:

Dijkstra算法是典型最短路算法,用於計算一個節點到其他所有節點的最短路徑。主要特點是以起始點為中心向外層層擴展,直到擴展到終點為止。Dijkstra算法能得出最短路徑的最優解,但由於它遍歷計算的節點很多,所以效率低。

Dijkstra算法是很有代表性的最短路算法,在很多專業課程中都作為基本內容有詳細的介紹,如數據結構,圖論,運籌學等等。

Dijkstra一般的表述通常有兩種方式,一種用永久和臨時標號方式,一種是用OPEN, CLOSE表方式,Drew為了和下面要介紹的 A* 算法和 D* 算法表述一致,這里均采用OPEN,CLOSE表的方式。

大概過程:
創建兩個表,OPEN, CLOSE。
OPEN表保存所有已生成而未考察的節點,CLOSED表中記錄已訪問過的節點。
1. 訪問路網中里起始點最近且沒有被檢查過的點,把這個點放入OPEN組中等待檢查。
2. 從OPEN表中找出距起始點最近的點,找出這個點的所有子節點,把這個點放到CLOSE表中。
3. 遍歷考察這個點的子節點。求出這些子節點距起始點的距離值,放子節點到OPEN表中。
4. 重復2,3,步。直到OPEN表為空,或找到目標點。

這是在drew 程序中4000個節點的隨機路網上Dijkstra算法搜索最短路的演示,黑色圓圈表示經過遍歷計算過的點由圖中可以看到Dijkstra算法從起始點開始向周圍層層計算擴展,在計算大量節點后,到達目標點。所以速度慢效率低。

提高Dijkstra搜索速度的方法很多,據Drew所知,常用的有數據結構采用Binary heap的方法,和用Dijkstra從起始點和終點同時搜索的方法。

基本思想 

引進兩個集合S和U。S的作用是記錄已求出最短路徑的頂點(以及相應的最短路徑長度),而U則是記錄還未求出最短路徑的頂點(以及該頂點到起點s的距離)。

操作步驟

(1) 初始時,S只包含起點s;U包含除s外的其他頂點,且U中頂點的距離為"起點s到該頂點的距離"[例如,U中頂點v的距離為(s,v)的長度,然后s和v不相鄰,則v的距離為∞]。

(2) 從U中選出"距離最短的頂點k",並將頂點k加入到S中;同時,從U中移除頂點k。

(3) 更新U中各個頂點到起點s的距離。之所以更新U中頂點的距離,是由於上一步中確定了k是求出最短路徑的頂點,從而可以利用k來更新其它頂點的距離;例如,(s,v)的距離可能大於(s,k)+(k,v)的距離。

(4) 重復步驟(2)和(3),直到遍歷完所有頂點。

單純的看上面的理論可能比較難以理解,下面通過實例來對該算法進行說明。

 

/*
測試數據 教科書 P189 G6 的鄰接矩陣 其中 數字 1000000 代表無窮大
6
1000000 1000000 10 100000 30 100
1000000 1000000 5 1000000 1000000 1000000
1000000 1000000 1000000 50 1000000 1000000
1000000 1000000 1000000 1000000 1000000 10
1000000 1000000 1000000 20 1000000 60
1000000 1000000 1000000 1000000 1000000 1000000
結果:
D[0]   D[1]   D[2]   D[3]   D[4]   D[5]
 0   1000000   10     50     30     60
*/
#include <stdio.h>
#define MAX 1000000

int arcs[10][10];//鄰接矩陣
int D[10];//保存最短路徑長度
int p[10][10];//路徑
int final[10];//若final[i] = 1則說明 頂點vi已在集合S中
int n = 0;//頂點個數
int v0 = 0;//源點
int v,w;
void ShortestPath_DIJ()
{
    for (v = 0; v < n; v++) //循環 初始化
    {
        final[v] = 0; D[v] = arcs[v0][v];
        for (w = 0; w < n; w++) p[v][w] = 0;//設空路徑
        if (D[v] < MAX) {p[v][v0] = 1; p[v][v] = 1;}
    }
    D[v0] = 0; final[v0]=1; //初始化 v0頂點屬於集合S
    //開始主循環 每次求得v0到某個頂點v的最短路徑 並加v到集合S中
    for (int i = 1; i < n; i++)
    {
        int min = MAX;
        for (w = 0; w < n; w++)
        {
            //我認為的核心過程--選點
            if (!final[w]) //如果w頂點在V-S中
            {
                //這個過程最終選出的點 應該是選出當前V-S中與S有關聯邊
                //且權值最小的頂點 書上描述為 當前離V0最近的點
                if (D[w] < min) {v = w; min = D[w];}
            }
        }
        final[v] = 1; //選出該點后加入到合集S中
        for (w = 0; w < n; w++)//更新當前最短路徑和距離
        {
            /*在此循環中 v為當前剛選入集合S中的點
               則以點V為中間點 考察 d0v+dvw 是否小於 D[w] 如果小於 則更新
               比如加進點 3 則若要考察 D[5] 是否要更新 就 判斷 d(v0-v3) + d(v3-v5) 的和是否小於D[5]
               */
            if (!final[w] && (min+arcs[v][w]<D[w]))
            {
                D[w] = min + arcs[v][w];
                // p[w] = p[v];
                p[w][w] = 1; //p[w] = p[v] + [w]
            }
        }
    }
}


int main()
{
    freopen("./Dijkstra.txt", "r", stdin);
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            scanf("%d", &arcs[i][j]);
        }
    }
    ShortestPath_DIJ();
    for (int i = 0; i < n; i++) printf("D[%d] = %d\n",i,D[i]);
    return 0;
}

 

6
1000000 1000000 10 100000 30 100
1000000 1000000 5 1000000 1000000 1000000
1000000 1000000 1000000 50 1000000 1000000
1000000 1000000 1000000 1000000 1000000 10
1000000 1000000 1000000 20 1000000 60
1000000 1000000 1000000 1000000 1000000 1000000

 

 

A*(A Star)算法:啟發式(heuristic)算法

A*(A-Star)算法是一種靜態路網中求解最短路最有效的方法。

公式表示為:        f(n)=g(n)+h(n), 
其中f(n) 是節點n從初始點到目標點的估價函數,
g(n) 是在狀態空間中從初始節點到n節點的實際代價,即起始節點到當前節點的實際代價.
h(n)是從n到目標節點最佳路徑的估計代價。即當前節點到目標節點的估計代價.

g(n):對g*(n)的一個估計,是當前的搜索圖G中s到n的最優路徑費用 g(n)≥g*(n)

h(n):對h*(n)的估計,是從n到目標節點的估計代價,稱為啟發函數。

例如:當h(n) = 0, g(n) = d, 則f(n) = g(n)就變為了寬度優先搜索,也就是如果不需要啟發,那就是寬度優先搜索的算法了。

.

g(n):對g*(n)的一個估計,是當前的搜索圖G中s到n的最優路徑費用 g(n)≥g*(n)

h(n):對h*(n)的估計,是從n到目標節點的估計代價,稱為啟發函數。

例如:當h(n) = 0, g(n) = d, 則f(n) = g(n)就變為了寬度優先搜索,也就是如果不需要啟發,那就是寬度優先搜索的算法了。

保證找到最短路徑(最優解的)條件,關鍵在於估價函數h(n)的選取:
估價值h(n)<= n到目標節點的距離實際值,這種情況下,搜索的點數多,搜索范圍大,效率低。但能得到最優解。
如果 估價值>實際值, 搜索的點數少,搜索范圍小,效率高,但不能保證得到最優解。
估價值與實際值越接近,估價函數取得就越好。
例如對於幾何路網來說,可以取兩節點間歐幾理德距離(直線距離)做為估價值,即f=g(n)+sqrt((dx-nx)*(dx-nx)+(dy-ny)*(dy-ny));這樣估價函數f在g值一定的情況下,會或多或少的受估價值h的制約,節點距目標點近,h值小,f值相對就小,能保證最短路的搜索向終點的方向進行。明顯優於Dijstra算法的毫無無方向的向四周搜索。

conditions of heuristic
Optimistic (must be less than or equal to the real cost)
As close to the real cost as possible

主要搜索過程:
創建兩個表,OPEN表保存所有已生成而未考察的節點,CLOSED表中記錄已訪問過的節點。
遍歷當前節點的各個節點,將n節點放入CLOSE中,取n節點的子節點X,->算X的估價值->

While(OPEN!=NULL)
{
從OPEN表中取估價值f最小的節點n;
if(n節點==目標節點) break;
else
{
if(X in OPEN) 比較兩個X的估價值f //注意是同一個節點的兩個不同路徑的估價值
if( X的估價值小於OPEN表的估價值 )
   更新OPEN表中的估價值; //取最小路徑的估價值

if(X in CLOSE) 比較兩個X的估價值 //注意是同一個節點的兩個不同路徑的估價值
if( X的估價值小於CLOSE表的估價值 )
   更新CLOSE表中的估價值; 把X節點放入OPEN //取最小路徑的估價值

if(X not in both)
求X的估價值;
   並將X插入OPEN表中; //還沒有排序
}

將n節點插入CLOSE表中;
按照估價值將OPEN表中的節點排序; //實際上是比較OPEN表內節點f的大小,從最小路徑的節點向下進行。
}

 

上圖是和上面Dijkstra算法使用同一個路網,相同的起點終點,用A*算法的情況,計算的點數從起始點逐漸向目標點方向擴展,計算的節點數量明顯比Dijkstra少得多,效率很高,且能得到最優解。

A*算法和Dijistra算法的區別在於有無估價值,Dijistra算法相當於A*算法中估價值為0的情況。

 

D*算法

待補充...

最短路徑算法

原文網址已經被占用.

 

Floyd(弗洛伊德)算法( from JarryWell)

Floyd算法是一個經典的動態規划算法。是解決任意兩點間的最短路徑(稱為多源最短路徑問題)的一種算法,可以正確處理有向圖或負權的最短路徑問題。(動態規划算法是通過拆分問題規模,並定義問題狀態與狀態的關系,使得問題能夠以遞推(分治)的方式去解決,最終合並各個拆分的小問題的解為整個問題的解。)

算法思想

從任意節點i到任意節點j的最短路徑不外乎2種可能:1)直接從節點i到節點j,2)從節點i經過若干個節點k到節點j。所以,我們假設arcs(i,j)為節點i到節點j的最短路徑的距離,對於每一個節點k,我們檢查arcs(i,k) + arcs(k,j) < arcs(i,j)是否成立,如果成立,證明從節點i到節點k再到節點j的路徑比節點i直接到節點j的路徑短,我們便設置arcs(i,j) = arcs(i,k) + arcs(k,j),這樣一來,當我們遍歷完所有節點k,arcs(i,j)中記錄的便是節點i到節點j的最短路徑的距離。(由於動態規划算法在執行過程中,需要保存大量的臨時狀態(即小問題的解),因此它天生適用於用矩陣來作為其數據結構,因此在本算法中,我們將不使用Guava-Graph結構,而采用鄰接矩陣來作為本例的數據結構)

for (int k = 1; k <= vexCount; k++) { //並入中轉節點1,2,...vexCount
    for (int i = 1; i <= vexCount; i++) {
        for (int j = 1; j < vexCount; j++) {
            if (arcs[i][k] + arcs[k][j] < arcs[i][j]) {
                arcs[i][j] = arcs[i][k] + arcs[k][j];
                path[i][j] = path[i][k]; //這里保存當前是中轉的是哪個節點的信息
            }
        }
    }
} 

 





免責聲明!

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



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