固然,這篇文章是借鑑大佬的。。。算法
最短路算法大約來講就是有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] = -1; 19 } 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)<dist(i) dist(j) < dist(i)dist(j)<dist(i),則將j插入隊首,不然插入隊尾。(deque)
LLL優化:Large Label Last 策略:設隊首元素爲i ii,每次彈出時進行判斷,隊列中全部dist值的平均值爲x,若dist(i)>x dist(i)>xdist(i)>x則將i ii插入到隊尾,查找下一元素,直到找到某一i ii使得dist(i)<=x dist(i)<=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優化的(主要是習慣了)