定義概覽
Dijkstra(迪傑斯特拉)算法是典型的單源最短路徑算法,用於計算一個節點到其他所有節點的最短路徑。主要特點是以起始點為中心向外層層擴展,直到擴展到終點為止。
問題描述:在無向圖 G=(V,E) 中,假設每條邊 E[i] 的長度為 w[i],找到由頂點 V0 到其余各點的最短路徑。(單源最短路徑)
算法描述
1)算法思想:
設G=(V,E)是一個帶權有向圖,把圖中頂點集合V分成兩組,第一組為已求出最短路徑的頂點集合(用S表示,初始時S中只有一個源點,以后每求得一條最短路徑 , 就將加入到集合S中,直到全部頂點都加入到S中,算法就結束了),第二組為其余未確定最短路徑的頂點集合(用U表示),按最短路徑長度的遞增次序依次把第二組的頂點加入S中。在加入的過程中,總保持從源點v到S中各頂點的最短路徑長度不大於從源點v到U中任何頂點的最短路徑長度。此外,每個頂點對應一個距離,S中的頂點的距離就是從v到此頂點的最短路徑長度,U中的頂點的距離,是從v到此頂點只包括S中的頂點為中間頂點的當前最短路徑長度。
2)算法步驟:
a.初始時,S只包含源點,即S={v},v的距離為0。U包含除v外的其他頂點,即:U={其余頂點},若v與U中頂點u有邊,則<u,v>正常有權值,若u不是v的出邊鄰接點,則<u,v>權值為∞。
b.從U中選取一個距離v最小的頂點k,把k,加入S中(該選定的距離就是v到k的最短路徑長度)。
c.以k為新考慮的中間點,修改U中各頂點的距離;若從源點v到頂點u的距離(經過頂點k)比原來距離(不經過頂點k)短,則修改頂點u的距離值,修改后的距離值的頂點k的距離加上邊上的權。
d.重復步驟b和c直到所有頂點都包含在S中。
執行動畫過程如下圖
動圖太快可以看下面的例子:
重點需要理解這句拗口的”按最短路徑長度的遞增次序依次把第二組的頂點加入S中。在加入的過程中,總保持從源點v到S中各頂點的最短路徑長度不大於從源點v到U中任何頂點的最短路徑長度”
實際上,Dijkstra 算法是一個排序過程,就上面的例子來說,是根據A到圖中其余點的最短路徑長度進行排序,路徑越短越先被找到,路徑越長越靠后才能被找到,要找A到F的最短路徑,我們依次找到了
A –> C 的最短路徑 3
A –> C –> B 的最短路徑 5
A –> C –> D 的最短路徑 6
A –> C –> E 的最短路徑 7
A –> C –> D –> F 的最短路徑 9
為什么Dijkstra 算法不適用於帶負權的圖?
就上個例子來說,當把一個點選入集合S時,就意味着已經找到了從A到這個點的最短路徑,比如第二步,把C點選入集合S,這時已經找到A到C的最短路徑了,但是如果圖中存在負權邊,就不能再這樣說了。舉個例子,假設有一個點Z,Z只與A和C有連接,從A到Z的權為50,從Z到C的權為-49,現在A到C的最短路徑顯然是A –> Z –> C
再舉個例子:
在這個圖中,求從A到C的最短路,如果用Dijkstra根據貪心的思想,選擇與A最接近的點C,長度為7,以后不再變化。但是很明顯此圖最短路為5。歸結原因是Dijkstra采用貪心思想,不從整體考慮結果,只從當前情況考慮選擇最優。
4.代碼模板
1 #include<stdio.h> 2 #include<string.h> 3 #define inf 0x3f3f3f3f 4 int map[110][110],dis[110],visit[110]; 5 /* 6 關於三個數組:map數組存的為點邊的信息,比如map[1][2]=3,表示1號點和2號點的距離為3 7 dis數組存的為起始點與每個點的最短距離,比如dis[3]=5,表示起始點與3號點最短距離為5 8 visit數組存的為0或者1,1表示已經走過這個點。 9 */ 10 int n,m; 11 int dijstra() 12 { 13 int i,j,pos=1,min,sum=0; 14 memset(visit,0,sizeof(visit));//初始化為.,表示開始都沒走過 15 for(i=1; i<=n; ++i) 16 { 17 dis[i]=map[1][i]; 18 } 19 visit[1]=1; 20 dis[1]=0; 21 for(i=1; i<n; i++) 22 { 23 min=inf; 24 for(j=1; j<=n; ++j) 25 { 26 if(visit[j]==0&&min>dis[j]) 27 { 28 min=dis[j]; 29 pos=j; 30 } 31 } 32 visit[pos]=1;//表示這個點已經走過 33 for(j=1; j<=n; ++j) 34 { 35 if(visit[j]==0&&dis[j]>dis[pos]+map[pos][j])//更新dis的值 36 dis[j]=dis[pos]+map[pos][j]; 37 } 38 } 39 return dis[n]; 40 } 41 int main() 42 { 43 int i,j; 44 while(~scanf("%d%d",&n,&m),n||m)//n表示n個點,m表示m條邊 45 { 46 for(i=1; i<=n; ++i) 47 { 48 for(j=1; j<=n; ++j) 49 { 50 map[i][j]=inf;//開始時將每條邊賦為最大值 51 } 52 } 53 int a,b,c; 54 for(i=1; i<=m; ++i) 55 { 56 scanf("%d%d%d",&a,&b,&c); 57 if(c<map[a][b])//防止有重邊 58 map[a][b]=map[b][a]=c; 59 } 60 int count=dijstra(); 61 printf("%d\n",count); 62 } 63 return 0; 64 }
鄰接表實現:
1 #include<stdio.h> 2 #include<string.h> 3 #include<vector> 4 #include<algorithm> 5 #define INF 0x3f3f3f3f 6 using namespace std; 7 struct node 8 { 9 int end;///終點 10 int power;///權值 11 } t; 12 int n;///n為點數 13 vector<node>q[500001];///鄰接表儲存圖的信息 14 int dis[500001];///距離 15 int vis[500001];///標記數組 16 void Dijkstra(int start,int end) 17 { 18 int i,len,j,pos; 19 memset(vis,0,sizeof(vis)); 20 for(i=0; i<=n; i++) 21 { 22 dis[i]=INF; 23 } 24 len=q[start].size(); 25 for(i=0; i<len; i++) 26 { 27 if(q[start][i].power<dis[q[start][i].end]) 28 { 29 dis[q[start][i].end]=q[start][i].power; 30 } 31 }///從起點開始的dis數組更新 32 vis[start]=1; 33 for(j=0; j<n-1; j++) 34 { 35 int pos,min=INF; 36 for(i=1; i<=n; i++) 37 { 38 if(vis[i]!=0&&dis[i]<min) 39 { 40 min=dis[i]; 41 pos=i;///找到未訪問節點中權值最小的 42 } 43 } 44 vis[pos]=1; 45 len=q[pos].size();///再次更新dis數組 46 for(j=0; j<len; j++) 47 { 48 if(vis[q[pos][j].end]==0&&dis[q[pos][j].end]>q[pos][j].power+dis[pos]) 49 { 50 dis[q[pos][j].end] = q[pos][j].power+dis[pos]; 51 } 52 } 53 } 54 printf("%d\n",dis[end]); 55 } 56 int main() 57 { 58 int m,i; 59 int begin,end,power; 60 int a,b; 61 while(scanf("%d%d",&n,&m)!=EOF) 62 { 63 for(i=0; i<=n; i++) 64 { 65 q[i].clear();///將victor數組清空 66 } 67 for(i=0; i<m; i++) 68 { 69 scanf("%d%d%d",&begin,&end,&power);///輸入 70 t.end=end; 71 t.power=power; 72 q[begin].push_back(t); 73 t.end=begin;///無向圖 74 t.power=power; 75 q[end].push_back(t); 76 } 77 scanf("%d%d",&a,&b);///輸入起點與終點 78 Dijkstra(a,b); 79 } 80 return 0; 81 }