據 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]; //這里保存當前是中轉的是哪個節點的信息 } } } }