資料結構與演算法20-圖的最短路徑
最短路徑
對於網圖來說,最短路徑,是指兩頂點之間經過的邊上權值之和最少的路徑,並且我們稱路徑上的第一個頂點是源點,最後一個頂點是終點。
迪傑斯特拉演算法
它並不是一下子就求出V0到V8的最短路徑,而一步步求出它們之間頂點的最短路徑,過程中都是基於已經求出的最短路徑的基礎上,求得更遠頂點的最短路徑,最終得到你要的結果。
我們來看一下思路
1. 頂點v0到v1的最短距離,答案是1,路徑就是直接v0連線到v1
2. 由於頂點v1還與v2、v3、v4連線,所以
我們同時求得了
v0->v1->v2=1+3=4,
v0->v1->v3=1+7=8
v0->v1->v4=1+5=6
現在,問v0到v2的最短距離是?答案是4
由於頂點v2還與v4、v5連線,所以此時我們同時求得v0->v2->v4 = 4+1=5
v0->v2->v5=4+7=11。這裡v0->v2我們用的是剛才計算出來 的較小的4。此時我們也發現v0->v1->v2->v4=5要比v0->v1->v4=6還要小。所以v0到v4目前的最小距離是5。如圖
當我們要求v0到v3的最短距離時,通向v3有三條邊,除v6沒研究過外,
v0->v1->v3=結果是8
v0->v4->v3=5+2=7。因此v0到v3的最短距離是7
我們來看一下程式碼模擬執行
#define MAXVEX 9 #define INFINITY 65535 typedef int Patharc[MAXVEX]; //用於儲存 最短路徑下標的陣列 typedef int ShortPathTable[MAXVEX]; //用於儲存到各點最短路徑的權值 /*Dijkstra演算法,求有向圖G的V0頂點到其餘頂點v最短路徑P[v]及帶權長度D[v]*/ /*P[v]的值為前驅頂點下標,D[v]表示v0到v的最短路徑長度和*/ void ShortesPath_Dijkstra(MGraph G,int v0,Patharc *P,ShortPathTable *D) { int v,w,k,min; int final[MAXVEX]; //final[w]=1表示頂點v0到vw的最短路徑 for(v=0;v<G.numVertexes;v++) { final[v]=0; //全部頂點初始化為未知最短路徑狀態 (*D)[v] = G.arc[v0][v]; (*P)[v] = 0; } (*D)[v0]=0; final[v0]=1; /**開始主迴圈,每次求得v0到某個v頂點的最短路徑/ for(v=1;v<G.numVertexes;v++) { min=INFIITY; for(w=0;w<G.numvertexes;w++) { if(!final[w]&&(*D)[w]<min) { k=w; min = (*D)[w]; } } final[k]=1; //將目前找到的最近的頂點置為1 for(w=0;w<G.numVertexes;w++) { if(!final[w]&&(min+G.arc[k][w]<(*D)[w])) { (*D)[w]=min*G.arc[k][w]; (*P)[w] = k; } } } }
分析過程:
1. 程式開始執行,第4行的final陣列是為了v0到某頂點是否已經求得最短路徑的標記,如果v0到vw已經有結果,則final[w]=1;
2. 第5~10行,是在對資料進行初始化的工作。此時final陣列值均為0,表示所有的點都未求得最短路徑。D陣列為{65535,1,5,65535,65535,65535,65535,65535}。因為v0與v1和v2的邊權值為1和5。P陣列全是0,表示目前沒有路徑。
3. 第11行,表示v0到v0自身,權值和結果為0。D陣列為{0,1,5,65535,65535,65535,65535,65535}。第12行,表示v0點算是已經求得最短路徑,因此final[0]=1。此時final陣列為{1,0,0,0,0,0,0,0,0}此時整個初始化操作完成
4. 第13行~33,為主迴圈,每次迴圈求得v0與一個頂點的最短路徑。因此v從1而不是0開始。
5. 先令min=65535的極大值,通過w迴圈,與D[w]比較找到最小值min=1,k=1。
6. 第24行,由k=1,表示與v0最近的頂點是v1,並且由D[1]=1,適量此時v0到v1的最短距離是1。因此將v1對應final[1]設定為1。此時final陣列為{1,1,0,0,0,0,0,0,0}
7. 第25~32行是一迴圈,此迴圈甚為關鍵。它的目的是在剛才已經找到的v0到v1的最短路徑的基礎上,對v1與其它頂點的邊進行計算,得到v0與它們的最短距離,如圖
因為此時min=1,所以本來的D[2]=5,現在v0->v1->v2=D[2]=min+3=4;
v0->v1->v3=D[3]=min+7=8, v0->v1->v4=D[4]=min+5=6,因此D陣列當前值為{0,1,4,8,6,65535, 65535, 65535, 65535}。而P[2]=1,P[3]=1,P[4]=1,它表示意思是v0到v2,v3,v4點的最短路徑它們的前驅是v1。此時P陣列值為{0,0,1,1,1,0,0,0,0}
8. 重新開始迴圈,此時v=2。第15~23行,對w迴圈,注意思為final[0]=1和final1[1]=1,由第18行的!final[w]可知,v0與v1並不參與最小值獲取。通過迴圈比較,找到最小值min=4,k=2。
9. 第24行,由k=2,表示已經求出v0到v2的最短路徑,並且由D[2]=4,知道最短距離是4。因此將v2對應的final[2]設定為1,此時final陣列為{1,1,1,0,0,0,0,0,0}
10. 第25~32行。在剛才已經找到V0與v2的最短路徑的基礎上,對v0與其他頂點的邊,進行計算,得到v0與它們的當前最短距離,如圖。因為min=4,所以本來D[4]=6,現在v0->v2->v4=D[4]=min+1 = 4; v0->v2->v5=D[4]=min+7 = 11,因此D陣列當前值為:{0,1,4,8,5,11, 65535, 65535, 65535}。而原來的P[4]=1而現在的P[4]=2,P[5]=2,它表示v0到v4、v5點的最短路徑它們的前驅均是v2。此時P陣列值為:{0,0,1,1,2,2,0,0,0}。
11. 重新開始迴圈,此時v=3。第15~23行,得到最小值min=5,k=4。
12. 第24行,由k=4已經求出v0到v4的最短路徑,並且由D[4]=5知道最短距離是5。因此將v4對應的final[4]設定為1。此時final陣列為{1,1,1,0,1,0,0,0,0}
13. 第25~32行,對v4與其他頂點的邊進行計算。得到v0與它們的當前最短距離,因為min=5,所以本來D[3]=8,現在v0->v4->v3=D[3]=min+2=7,本來的D[5]=11,現在的v0->v4->v5=D[5]=min+3=8,另外v0->v4->v6=D[6]=min+6=11,v0->v4->v7=D[7]=min+9=14。因此D陣列的當前值為:{0,1,4,7,5,8,11,14,65535}。而原本的P[3]=1,此時P[3]=4,原本的P[5]=2,此時的P[5]=4,另外P[6]=4,P[7]=4,它表示v0到v3、v5、v6、v7點的最短路徑它們的前驅是v4。此時P陣列的值為:{0,0,1,4,2,4,4,4,0}
14. 之後的迴圈就是完全類似的了。得到最終結果
final陣列{1,1,1,1,1,1,1,1,1},它表示所有頂點均完成了最短路徑的查詢工作。此時D陣列為{0,1,4,7,5,8,10,12,16},它表示v0到各個頂點的最短路徑數,
此時的P陣列為{0,0,1,4,2,4,3,6,7}。比如P[8]=7它的意思是V0到V8的最短路徑,頂點v8的前驅頂點是v7,再由P[7]=6,表示v7的前驅是v6,P[6]=3,前驅是v3。這樣就可以得到,v0到v8的最短路徑為v8<-v7<-v6<-v3<-v2<-v1<-v0即v0->v1->v2->v4->v3->v6->v7->v8。
其實最終返回的陣列D和P,是可以得到V0到任意一個頂點的最短路徑和路徑長度的。例如v0到v8的最短路徑並沒有經過v5,但我們已經知道v0到v5的最短路徑。D[5]=8,由P[5]可知道它的前驅頂點是v4。所以v0到v5的最短路徑是v0->v1->v2->v4->v5。
迪傑斯特拉(Dijkstra)演算法解決了從某個源點到其餘頂點的最短路徑問題。時間複雜度O(n2)
現在如果我想求任決一點到其它點的最短路徑。則還得有一個迴圈來處理複雜度就變成了O(n3)
那麼我們再求一種求最短路徑的演算法如下
弗洛伊德(Floyd),它求所有頂點到所有頂點的時間複雜度也是O(n3),但其演算法非常簡潔優雅。
弗洛伊德(Floyd)演算法
我們先來看一個簡單的安全如下圖的3個頂點的連通網圖。
我們先定義兩個二維陣列D[3][3]和P[3][3],D代表頂點到頂點的最短路徑權值
P代表對應頂點的最小路徑前驅矩陣。
在未分析任何頂點前,我們將D命名為D-1其實它就是初始的圖的鄰接矩陣。將P命名為P-1,初始化為圖中所示的矩陣
首先我們來分析,所有頂點經過v0後到達另一頂點的最短路徑。因為只有三個頂點,因此需要檢視v1->v0->v2,得到D-1[1][0]+D-1[0][2]=2+1=3。
D-1[1][2]表示的是v1->v2的權值為5,我們發現D-1[1][0]>D-1[1][0]+D-1[0][2]。通俗的說就是v1->v0->v2比直接v1->v2的距離還要近。所以我們就讓D-1[1][2]=D-1[1][0]+D-1[0][2]=3,同樣的D-1[2][1]也等於3。於是就有了D0的矩陣
因為有了變化,所以P矩陣對應的P-1[1][2]和P-1[2][1]也修改為當前中轉的頂點v0的下標0,於是就有了P0看上圖的轉換過程
接下來有了D0和P0的基礎上繼續處理所有頂點經過v1和v2後到另一頂點的最短路徑,得到D1和P1、D2和P2完成所有頂點 到所有頂點的最短路徑計算工作
下面來個複雜的圖的推演
程式碼如下
typedef int Pathmatirx[MAXVEX][MAXVEX];
typedef int ShortPathTable[MAXVEX][MAXVEX];
/*Floyd演算法,求網圖G中各頂點v到其餘頂點w最短路徑P[v][w]及帶權長度D[v][w]*/
void ShortestPath_Floyd(MGraph G,Pathmatirx *P,ShortPathTable *D)
{
int v,w,k;
for(w=0;v<G.numVertexes;++v)
{
for(w=0;w<G.numVertexes;++w)
{
(*D)[v][w] = G.matirx[v][w]; //初始化,即為對應點間的權值
(*P)[v][w] = w; //初始化P
}
}
for(k=0;k<G.numVertexes;++k)
{
for(v=0;v<G.numVertexes;++v)
{
for(w=0;w<G.numVertexes;++w)
{
if((*D)[v][w]>(*D)[v][k]+(*D)[k][w])
{
(*D)[v][w] = (*D)[v][k]+(*D)[k][w];
(*P)[v][w]=(*P)[v][k];
}
}
}
}
}
1. 程式開始執行,第4~11行就是初始化D和P,使得它們成為下圖的兩個矩陣。從矩陣也得到v0->v1路權值是1,v0->v2路權值是5,v0->v3無邊連線,所以路徑權值為極大值65535。
2. 第12~25行,是演算法的主迴圈,一共三個巢狀,k代表的就是中轉頂點的下標。v代表起始頂點,w代表結束頂點。
3. 當K=0時,也就是所有的頂點都經過v0中轉,計算是否有最短路徑的變化。可惜結果是,沒有任何變化,如下圖
4. 當K=1時,也就是所有的頂點都經過v1中轉。此時,當v=0,原本D[0][2]=5,現在由於D[0][1]+D[1][2]=4。因此由程式碼的第20行,二者取其最小值,得到D[0][2]=4,同理可得D[0][3]=8、D[0][4]=6,當v=2、3、4時,也修改一些資料。對權值進行修改也要對P[v][k]
至此,我們的最短路徑就算是完成了,你可以看到矩陣第v0行的數值與迪傑斯特拉演算法求得的D陣列是完全相同的{0,1,4,7,5,8,10,12,16}
路徑v0到v8 P[0][8]=1 先經過v1,然後將1取代0得到P[1][8]=2說明經過2,然後以2取代1 P[2][8]=4說明經過4,然後以4取代2 P[4][8]=3說明經過3…
最後昨到v0->v1->v2->v4->v3->v6->v7->v8