1. 程式人生 > >圖的算法專題——最短路徑

圖的算法專題——最短路徑

說明 turn () path 刪除 鄰接矩陣 else bool 短路徑

概要:

  1. Dijkstra算法
  2. Bellman-Ford算法
  3. SPFA算法
  4. Floyd算法


1、Dijkstra算法用於解決單源最短路徑問題,嚴格講是無負權圖的最短路徑問題。

鄰接矩陣版

 1 const int maxv=1000;
 2 const int INF=1000000000;
 3 int n,G[maxv][maxv];
 4 int d[maxv];  //起點到各點的最短路徑長度
 5 bool vis[maxv]={false};
 6 
 7 void Dijkstra(int s){ //s為起點
 8     fill(d,d+maxv,INF);
 9     d[s]=0
; 10 for(int i=0;i<n;i++){ //循環n次 11 int u=-1,MIN=INF; //u使d[u]最小,MIN存放最小d[u] 12 for(int j=0;j<n;j++){ 13 if(vis[j]==false && d[j]<MIN){ //未收錄的頂點中到起點距離最小者 14 u=j; 15 MIN=d[j]; 16 } 17 } 18 //找不到小於INF的d[u],說明剩下的頂點和起點s不連通
19 if(u==-1) return; 20 vis[u] =true; 21 for(int v=0;v<n;v++){ 22 if( G[u][v]!=INF && vis[v]==false && d[u]+G[u][v] < d[v]){ 23 d[v]=d[u]+G[u][v]; 24 } 25 } 26 } 27 }

鄰接表版

 1 struct Node{
 2     int v,dis; //
v為邊的目標頂點, dis為邊權 3 }; 4 vector<Node> Adj[maxv]; 5 int n,d[maxv]; 6 bool vis[maxv]={0}; 7 8 void Dijkstra(int s){ 9 fill(d,d+maxv,INF); 10 d[s]=0; 11 for(int i=0;i<n;i++) { 12 int u=-1,MIN=INF; 13 for(int j=0;j<n;j++){ 14 if(d[j]<MIN && vis[j]==false){ 15 u=j; 16 MIN=d[j]; 17 } 18 } 19 if(u==-1) return; 20 vis[u]=true; 21 for(int j=0;j<Adj[u].size();j++){ 22 int v=Adj[u][j].v; 23 if(vis[v]==false && d[u] +Adj[u][j].dis <d[v]){ 24 d[v]=d[u]+Adj[u][j].dis; 25 } 26 } 27 } 28 }

若要求輸出最短路徑,以鄰接矩陣為例:

 1 const int maxv=1000;
 2 const int INF=1000000000;
 3 int n,G[maxv][maxv];
 4 int d[maxv];  //起點到各點的最短路徑長度
 5 bool vis[maxv]={false};
 6 int pre[maxv];
 7 
 8 void Dijkstra(int s){ //s為起點
 9     fill(d,d+maxv,INF);
10     for(int i=0;i<n;i++) pre[i]=i;
11     d[s]=0;
12     for(int i=0;i<n;i++){ //循環n次
13         int u=-1,MIN=INF;   //u使d[u]最小,MIN存放最小d[u]
14         for(int j=0;j<n;j++){
15             if(vis[j]==false && d[j]<MIN){ //未收錄的頂點中到起點距離最小者
16                 u=j;
17                 MIN=d[j];
18             }
19         }
20         //找不到小於INF的d[u],說明剩下的頂點和起點s不連通
21         if(u==-1) return;
22         vis[u] =true;
23         for(int v=0;v<n;v++){
24             if( G[u][v]!=INF && vis[v]==false && d[u]+G[u][v] < d[v]){
25                 d[v]=d[u]+G[u][v];
26                 pre[v]=u;
27             }
28         }
29     }
30 }
31 
32 void DFS(int s,int v){  //從終點開始遞歸
33     if(v==s){ //如果當前已經到達起點,輸出起點並返回
34         printf("%d\n",s);
35     }
36     DFS(s,pre[v]);
37     printf("%d\n",v);
38 }

另外還有一種情況,如果某個結點存在多個前驅結點,那上面這種pre數組的方法就不再適用,改成vector即可:

 1 const int maxv=1010;
 2 const int INF=1000000000;
 3 vector<int> pre[maxv];
 4 void Dijkstra(int s){
 5     fill(d,d+maxv,INF);
 6     d[s]=0;
 7     for(int i=0;i<n;i++){
 8         int u=-1,MIN=INF;
 9         for(int j=0;j<n;j++){
10             if(vis[j]==false && d[j]<MIN){
11                 u=j;
12                 MIN=d[j];
13             }
14         }
15         if(u==-1) return;
16         vis[u]=true;
17         for(int v=0;v<n;v++){
18             if(vis[v]==false &&G[u][v]!=INF){
19                 if(d[u]+G[u][v]<d[v]){
20                     d[v]=d[u]+G[u][v];
21                     pre[v].clear(); 
22                     pre[v].push_back(u);
23                 }
24                 else if(d[u]+G[u][v]==d[v]){
25                     pre[v].push_back(u);
26                 }
27             }
28         }
29     }
30 }

當訪問的結點是路徑起點st時(邊界),此時tempPath裏存了整條路徑(倒序),這時需要計算第二標尺value的值,並與optValue比較,若更優則更新optValue並把path覆蓋。

 1 const int maxv=1010;
 2 const int INF=1000000000;
 3 int optValue;
 4 vector<int> path,tempPath;
 5 vector<int> pre[maxv];
 6 
 7 void Dijkstra(int s){
 8     fill(d,d+maxv,INF);
 9     d[s]=0;
10     for(int i=0;i<n;i++){
11         int u=-1,MIN=INF;
12         for(int j=0;j<n;j++){
13             if(vis[j]==false && d[j]<MIN){
14                 u=j;
15                 MIN=d[j];
16             }
17         }
18         if(u==-1) return;
19         vis[u]=true;
20         for(int v=0;v<n;v++){
21             if(vis[v]==false &&G[u][v]!=INF){
22                 if(d[u]+G[u][v]<d[v]){
23                     d[v]=d[u]+G[u][v];
24                     pre[v].clear();
25                     pre[v].push_back(u);
26                 }
27                 else if(d[u]+G[u][v]==d[v]){
28                     pre[v].push_back(u);
29                 }
30             }
31         }
32     }
33 }
34 
35 void DFS(int v){ //v為當前訪問結點
36     if(v==st){
37         tempPath.push_back(v);
38         int value;
39         (計算路徑的value)
40         if(value優於optValue){
41             path=tempPath;
42             optValue=value;
43         }
44         tempPath.pop_back(); //將剛加入的結點刪除
45         return;
46     }
47     tempPath.push_back(v);
48     for(int i=0;i<pre[v].size();i++){
49         DFS(pre[v][i]);
50     }
51     tempPath.pop_back();
52 }

除此之外,還會碰到第二標尺,常見有以下三種:(具體代碼見晴神算法筆記,寫的很清楚)

  • 新增邊權(如增加開銷)
  • 新增點權(如收集到的物資)
  • 求最短路徑條數


圖的算法專題——最短路徑