最短路(Dijkstra,Floyd,Bellman_Ford,SPFA)

固然,這篇文章是借鑑大佬的。。。算法

最短路算法大約來講就是有4種——Dijkstra,Floyd,Bellman_Ford,SPFA數組

接下來,就能夠一一看一下。。。優化

1.Dijkstra(權值非負,適用於有向圖及無向圖,單源最短路)spa

1 Dijkstra's算法解決的是圖中單個源點到其它頂點的最短路徑。只能解決權值非負(看了代碼就知道了)
2 Dijkstral只能求出任意點到達源點的最短距離(不能求出任意兩點之間的最短距離),同時適用於有向圖和無向圖,複雜度爲O(n^2).
3算法的過程: 
1設置頂點集合S並不斷的做貪心選擇來選擇擴充這個集合。一個頂點屬於集合S當且僅當從源點到該點的最短路徑長度已知
2 初始時,S中僅含有源。設U是G的某一個頂點,把從源到U且中間只通過S中的頂點的路稱爲從源到U的特殊路徑,並用dis數組距離當前每個頂點所對應的最短特殊路徑
3Dijkstra算法每一次從V-S中取出具備最短特殊長度的頂點u,將u添加到S中,同時對dis數組進行修改。一旦S包含了全部的V中的頂點,dis數組就記錄了從源點到其它頂點的最短路徑長度。
4 模板:
沒有優化,時間複雜度o(n^2)
code

 1 #define MAXN 1010
 2 #define INF 0xFFFFFFF
 3 int  value[MAXN][MAXN];/*保存的是邊權值*/
 4 int  dis[MAXN];/*保存源點到任意點之間的最短路*/
 5 int  father[MAXN];/*保存i點的父親節點*/
 6 int  vis[MAXN];/*記錄頂點是否沒取過*/ 
 7  
 8 void input(){     
 9       int star , end , v;
10       scanf("%d%d" , &n , &m);
11       /*初始化value數組*/
12      for(int i = 1 ; i <= n ; i++){
13          for(int j = 1; j <= n ; j++)
14              value[i][j] = INF;
15      }
16     for(int i = 0 ; i < m ; i++){
17         scanf("%d%d%d" ,  &star , &end , &v);
18         if(value[star][end] == INF)
19             value[star][end] = value[end][star] = v;/*處理成無向圖*/
20         else{
21             if(v < value[star][end])/*判斷重邊是否出現*/
22                  value[star][end] = value[end][star] = v;
23         }
24 }
25  
26 void dijkstra(int s){
27      memset(vis , 0 , sizeof(vis));
28      memset(father , 0 , sizeof(father));
29      /*初始化dis數組*/
30      for(int i = 1 ; i<= n ; i++)
31           dis[i] = INF;
32      dis[s] = 0;
33      for(int i = 1 ; i <= n ; i++){/*枚舉n個頂點*/
34         int pos;
35         pos = -1;
36         for(int j = 1 ; j <= n ;j++){/*找到未加入集合的最短路點*/
37            if(!vis[j] && (pos == -1 || dis[j] < dis[pos]))
38               pos = j;
39         }
40         vis[pos] = 1;/*把這個點加入最短路徑集合*/
41         for(int j = 1 ; j <= n ; j++){/*更新dis數組*/
42            if(!vis[j] && (dis[j] > dis[pos] + value[pos][j])){
43              dis[j] = dis[pos] + value[pos][j];
44              father[j] = pos;
45            }
46         }
47      }
48 }

固然,確定是得有優化過的。。。時間複雜度o(mlogn),這裏用的是鄰接表優化,固然最天然仍是得屬鏈式前向星。blog

 1 優化過的,時間複雜度爲o(mlogn);
 2 /*利用鄰接表來優化*/
 3 #include<utility> 
 4 typedef pair<int , int>pii;/*pair專門把兩個類型捆綁在一塊兒的*/
 5 priority_queue<pii,vector<pii>,greater<pii> >q;/*優先隊列默認使用「<」,那麼優先隊列的元素是從大到小,因此本身定義「>」比較,STL中能夠用greater<int>來表示">",這樣就能夠來聲明一個小整數先出隊的優先隊列*/
 6 #define MAXN 1010
 7 #define INF 0xFFFFFFF
 8 int n , m;/*有n個點,m條邊*/
 9 int first[MAXN] , next[MAXN];/*first數組保存的是節點i的第一條邊,next保存的是邊e的下一條邊*/
10 int u[MAXN] , v[MAXN] , value[MAXN];
11 int vis[MAXN];
12 int dis[MAXN];
13  
14 /*讀入這個圖*/
15 void input(){
16      scanf("%d%d" , &n , &m);
17      /*初始化表頭*/
18      for(int i = 1 ; i <= n ; i++)
19         first[i] = -1;
20      for(int i = 1 ; i <= m ; i++){
21         scanf("%d%d" , &u[i] , &v[i] , &value[i]);       
22         next[i] = first[u[i]];/*表頭日後移動*/
23         first[u[i]] = i;/*更新表頭*/
24      }
25 }
26  
27 /*Dijkstra*/
28 void Dijkstra(int s){
29      memset(vis , 0 , sizeof(vis));
30      /*初始化點的距離*/
31      for(int i = 1 ; i <= n ; i++)
32           dis[i] = INF;
33     dis[s] = 0;
34      while(!q.empty())
35         q.pop();
36      q.push(make_pair(dis[s] , s));
37      while(!q.empty()){
38           pii u = q.top();
39           q.pop();
40           int x = u.second;
41           if(vis[x])
42             continue; 
43           vis[x] = 1;
44           for(int i = first[x] ; i != -1 ; i = next[i]){
45              if(dis[v[e]] > dis[x] + value[i]){
46                 dis[v[i]] = dis[x] + value[i];
47                 q.push(make_pair(dis[v[i] , v[i]));
48              }
49           }
50      }
51 }

2.Floyd(權值非負,適用於有向圖及無向圖,任意兩點最短路)隊列

1 floyd 的思想就是經過枚舉n個點利用DP的思想來更新最短距離的,假設當前枚舉到第k個點,那麼就有任意的兩個點i , j ,若是i k 相連 j k 相連 那麼就能夠知道這個時候dis[i][j] = min(dis[i][j] , dis[i][k] + dis[k][j]);,那麼只要枚舉完n個點,那麼就說明已經徹底更新完全部兩點直間的最短路。ci

2 floyd算法是最簡單的最短路徑的算法,能夠計算圖中任意兩點間的最短路徑。floyd算法的時間複雜度爲o(n^3),若是是一個沒有邊權的圖,把相連的兩點間的距離設爲dis[i][j]=1.不相連的兩點設爲無窮大,用floyd算法能夠判斷i j兩點是否相連。
3 floyd 算法不容許全部的權值爲負的迴路。能夠求出任意兩點之間的最短距離。處理的是無向圖
4 缺點是時間複雜度比較高,不適合計算大量數據input

5 若是dis[i][i] != 0,說明此時存在環。 it

6 若是利用floyd求最小值的時候,初始化dis爲INF , 若是是求最大值的話初始化爲-1.

7 模板:時間複雜度o(n^3)

 1 #define INF 0xFFFFFFF
 2 #define MAXN 1010
 3 int dis[MAXN][MAXN];
 4  
 5 /*若是是求最小值的話,初始化爲INF便可*/
 6 void init(){
 7      for(int i = 1 ; i <= n ; i++){
 8         for(int j = 1 ; j <= n ; j++)
 9              dis[i][j] = INF;
10         dis[i][i] = 0;
11      }
12 }
13  
14 /*若是是求最大值的話,初始化爲-1*/
15 void init(){
16      for(int i = 1 ; i <= n ; i++){
17         for(int j = 1 ; j <= n ; j++)
18              dis[i][j] = -119      }
20 }
21  
22 /*floyd算法*/
23 void folyd(){
24         for(int k = 1 ; k <= n ; k++){/*枚舉n個點來更新dis*/
25             for(int i = 1 ; i <= n ; i++){
26                 for(int j = 1 ; j <= n ; j++)
27                   if(dis[i][k] != -1 && dis[j][k] != -1)/*若是在求最大值的時候加上這一句*/
28                      dis[i][j] = min(dis[i][k]+dis[k][j] , dis[i][j]); 
29             } 
30        }
31  }

固然,通常的最短路應該沒有那麼得emmZZ優秀,確定仍是得有一個擴展來求最短路徑的 因此。。。

如何用floyd找出最短路徑所行經的點: 1 這裏要用到另外一個矩陣P,它的定義是這樣的:p(ij)的值若是爲p,就表示i到j的最短行經爲i->p...->j,也就是說p是i到j的最短行徑中的j以前的第1個點。

2 P矩陣的初值爲p(ij) = j。有了這個矩陣以後,要找最短路徑就垂手可得了。對於i到j而言找出p(ij),令爲p,就知道了路徑i->p....->j;再去找p(pj),若是值爲q,p到j的最短路徑爲p->q...->j;再去找p(qj),若是值爲r,i到q的最短路徑爲q>r...->q;因此一再反覆,就會獲得答案。

3  可是,如何動態的回填P矩陣的值呢?回想一下,當d(ij)>d(ik)+d(kj)時,就要讓i到j的最短路徑改成走i->...->k->...->j這一條路,可是d(ik)的值是已知的,換句話說,就是i->...->k這條路是已知的,因此i->...->k這條路上k的第一個城市(即p(ik))也是已知的,固然,由於要改走i->...->k->...->j這一條路,p(ij)的第一個城市正好是p(ik)。因此一旦發現d(ij)>d(ik)+d(kj),就把p(ik)存入p(ij).

4 代碼:

 1 int dis[MAXN][MAXN];
 2 int path[MAXN][MAXN];
 3  
 4 void floyd(){
 5       int  i, j, k;
 6       /*先初始化化爲j*/
 7       for (i = 1; i <= n; i++){
 8            for (j = 1; j <= n; j++)
 9                path[i][j] = j;
10       }
11       for (k = 1; k <= n; k++){/*枚舉n個點*/
12           for (i = 1; i <= n; i++){
13               for (j = 1; j <= n; j++){
14                    if (dis[i][j] > dis[i][k]+dis[k][j]){
15                          path[i][j] = path[i][k];/*更新爲path[i][k]*/
16                          dis[i][j] = dis[i][k]+dis[k][j];
17                     }
18               }
19            }
20       }
21 }

牛逼的是,Floyd仍是能夠求最小環的,但本蒟蒻仍是比較稀飯用並查集求最小環

1 爲何要在更新最短路以前求最小環:
在第k層循環,咱們要找的是最大結點爲k的環,而此時Dist數組存放的是k-1層循環結束時的通過k-1結點的最短路徑,也就是說以上求出的最短路是不通過k點的,這就恰好符合咱們的要求。爲何呢?假設環中結點i,j是與k直接相連,若是先求出通過k的最短路,那麼會有這樣一種狀況,即:i到j的最短路通過k。這樣的話就造成不了環 。

2最小環改進算法的證實:
一個環中的最大結點爲k(編號最大),與他相連的兩個點爲i,j,這個環的最短長度爲g[i][k]+g[k][j]+dis[i][j] (i到j的路徑中,全部結點編號都小於k的最短路徑長度)。根據floyd的原理,在最外層循環作了k-1次以後,dist[i][j]則表明了i到j的路徑中,全部結點編號都小於k的最短路徑, 綜上所述,該算法必定能找到圖中最小環。

3 爲何還要value數組:
由於dis數組時刻都在變更不能表示出原來兩個點之間的距離。

4 造成環至少要有3點不一樣的點,兩個點是不能算環的。
5 代碼:

 1 int mincircle = INF;
 2 int dis[MAXN][MAXN];
 3 int value[MAXN][MAXN];
 4  
 5 void floyd(){
 6      memcpy(value , dis , sizeof(value));
 7      for(int k = 1 ; k <= n ; k++){
 8           /*求最小環,不包含第k個點*/
 9           for(int i = 1 ; i < k ; i++){/*到k-1便可*/
10                for(int j = i+1 ; j < k ; j++)/*到k-1便可*/
11                    mincircle = min(mincircle , dis[i][j]+value[i][k]+value[k][j]);/*無向圖*/
12                    mincircle = min(mincircle , dis[i][j] +value[i][k]+value[k][j]);/*表示i->k , k->j有邊*/
13            }
14           /*更新最短路*/
15           for(int i = 1 ; i <= n ; i++){
16                for(int j = 1 ; j <= n ; j++)
17                    dis[i][j] = min(dis[i][k]+dis[k][j] , dis[i][j]);
18           }
19      }  
20 }

3.Bellman_Ford(權值可正可負,用來判斷負環,有向圖與無向圖,單源最短路)

1 Bellman_Frod能夠計算邊權爲負值的最短路問題,適用於有向圖和無向圖.用來求解源點到達任意點的最短路。

2 在邊權可正可負的圖中,環有零環,正環,負環3種。若是包含零環和正環的話,去掉之後路徑不會變成,若是包含負環則最短路是不存在的。那麼既然不包含負環,因此最短路除了源點意外最多隻通過n-1個點,這樣就能夠經過n-1次的鬆弛獲得源點到每一個點的最短路徑。

3 時間複雜度o(n*m);
4 若是存在環的話就是通過n-1次鬆弛操做後還能更新dis數組。
5 模板:

 1 #define INF 0xFFFFFFF
 2 #define MAXN 1010*2
 3 int dis[MAXN];/*dis[i]表示的是源點到點i的最短路*/
 4 strucu Edge{
 5    int x;
 6    int y;
 7    int value;
 8 }e[MAXN];
 9  
10 /*返回最小值*/
11 int min(int a , int b){
12     return a < b ? a : b;
13 }
14  
15 /*處理成無向圖*/
16 void input(){
17      for(int i = 0 ; i < m ; i++){
18         scanf("%d%d%d" , e[i].x , &e[i].y , &e[i].value);
19         e[i+m].x = e[i].y;/*注意地方*/
20         e[i+m].y = e[i].x;/*注意地方*/
21         e[i+m].value = e[i].value;          
22      }
23 }
24  
25 /*假設如今有n個點,m條邊*/
26 void Bellman_Ford(int s){
27     /*初始化dis數組*/
28     for(int i = 1 ; i <= s ; i++)
29          dis[i] = INF;
30     dis[s] = 0;
31     for(int i = 1 ; i < n ; i++){/*作n-1次鬆弛*/
32        for(int j = 0 ; j < 2*m ; j++){/*每一次枚舉2*m條邊,由於是無向圖*/
33           if(dis[e[j].y] > dis[e[j].x] + e[j].value)
34            dis[e[j.].y] = dis[e[i].x]+e[j].value;/*更新dis[e[j].y]*/
35           }
36        }
37     }
38 }

4.SPFA(權值可正可負,斷定負環,有向圖和無向圖,單源最短路)

本蒟蒻仍是比較稀飯SPFA的,至少是Bellman_Frod的升級版,而且lll優化和slf優化使得其很是牛X優秀

 1 #define MAXN 1010
 2 #define INF 0xFFFFFFF
 3  
 4 int n , m;
 5 int first[MAXN] , next[MAXN];
 6 int star[MAXN] , end[MAXN] , value[MAXN];
 7 int dis[MAXN];
 8 queue<int>q;
 9  
10 /*輸入*/
11 void input(){
12      scanf("%d%d" , &n , &m);
13      /*初始化表頭*/
14      for(int i = 1 ; i <= n ; i++){
15         first[i] = -1;
16         next[i] = -1;
17      }
18      /*讀入m條邊*/
19      for(int i = 0 ; i < m ; i++){
20         scanf("%d%d%d" , &star[i] , &end[i] , &value[i]);
21         star[i+m] = end[i];
22         end[i+m] = star[i];
23         value[i+m] = value[i];
24  
25         next[i] = first[star[i]];/*因爲要插入表頭,因此將原先表頭後移*/
26         first[star[i]] = i;/*插入表頭*/
27         next[i+m] = first[star[i+m]];
28         first[star[i+m]] = i+m;
29      }
30 }
31  
32 /*SPFA*/
33 void SPFA(int s){
34      while(!q.empty())
35           q.pop();
36      int vis[MAXN];
37      memset(vis , 0 , sizeof(vis));
38      /*初始化dis*/
39      for(int i = 1 ; i <= n ; i++)
40           dis[i] = INF;
41      dis[s] = 0;
42      q.push(s);/*將源點加入隊列*/
43      vis[s] = 1;/*源點標記爲1*/
44      while(!q.empty()){
45           int x = q.front();
46           q.pop();
47           vis[x] = 0;/*注意這裏要從新標記爲0,說明已經出隊*/
48           /*枚舉和點x有關的全部邊*/
49           for(int i = first[x] ; i != -1 ; i = next[i]){
50              if(dis[end[i]] > dis[x] + value[i]){/*鬆弛操做,利用三角不等式*/
51                dis[end[i]] = dis[x] + value[i];
52                if(!vis[end[i]]){/*若是該點再也不隊列裏面*/
53                   vis[end[i]] = 1;                
54                   q.push(end[i]);
55                }
56              }
57           }
58      }

 既然已經在前面提到過SPFA的兩種優化方法了,這裏就來YY介紹一下

SLF優化(雙端隊列deque優化):Small Label First 策略:設要加入的節點是j jj,隊首元素爲i ii,若dist(j)&lt;dist(i) dist(j) &lt; dist(i)dist(j)<dist(i),則將j插入隊首,不然插入隊尾。(deque)
LLL優化:Large Label Last 策略:設隊首元素爲i ii,每次彈出時進行判斷,隊列中全部dist值的平均值爲x,若dist(i)>x dist(i)&gt;xdist(i)>x則將i ii插入到隊尾,查找下一元素,直到找到某一i ii使得dist(i)&lt;=x dist(i)&lt;=xdist(i)<=x,則將i出對進行鬆弛操做。

 1 char str[maxn][maxn];
 2 int vis[maxn][maxn],dis[maxn][maxn],n,m;
 3 int ans[30],sum,cnt;
 4 int dx[] = {-1,-1,0,1,1,1,0,-1},dy[] = {0,1,1,1,0,-1,-1,-1};
 5 deque<PII>q;
 6 void spfa()
 7 {
 8     while(!q.empty()){
 9         PII f = q.front();q.pop_front();
10         //LLL優化
11         if(dis[f.fi][f.se] * cnt > sum){
12             q.push_back(f);
13             continue;
14         }
15         sum -= dis[f.fi][f.se];cnt--;
16         vis[f.fi][f.se] = 0;
17         for( int i = 0; i < 8; i++ ){
18             int nx = f.fi + dx[i],ny = f.se + dy[i];
19             if(nx < 1 || nx > n || ny < 1 || ny > m)continue;
20             int w = (str[nx][ny] != str[f.fi][f.se]);
21             if(dis[nx][ny] > dis[f.fi][f.se] + w){
22                 dis[nx][ny] = dis[f.fi][f.se] + w;
23                 if(!vis[nx][ny]){
24                     vis[nx][ny] = 1;
25                     //SLF優化
26                     if(dis[nx][ny] < dis[q.front().fi][q.front().se]){
27                         q.push_front(mp(nx,ny));
28                     }
29                     else {
30                         q.push_back(mp(nx,ny));
31                     }
32                     sum += dis[nx][ny];cnt++;
33                 }
34             }
35         }
36     }
37 }
38 void init()
39 {
40     cl(dis,INF);
41     cl(vis,0);
42     cl(ans,INF);
43     sum = cnt = 0;
44 }

本蒟蒻仍是比較稀飯slf優化的(主要是習慣了)

相關文章
相關標籤/搜索