想必大家一定會Floyd了吧,Floyd只要暴力的三個for就可以出來,代碼好背,也好理解,但缺點就是時間復雜度高是O(n³)。
於是今天就給大家帶來一種時間復雜度是O(n²),的算法:Dijkstra(迪傑斯特拉)。
這個算法所求的是單源最短路,好比說你寫好了Dijkstra的函數,那么只要輸入點a的編號,就可算出圖上每個點到這個點的距離。
我先上一組數據(這是無向圖):
5 6 1 2 5 1 3 8 2 3 1 2 4 3 4 5 7 2 5 2
圖大概是這個樣子:
Dijkstra 算法是一種類似於貪心的算法,步驟如下:
1、當到一個時間點時,圖上部分的點的最短距離已確定,部分點的最短距離未確定。
2、選一個所有未確定點中離源點最近的點,把他認為成最短距離。
3、再把這個點所有出邊遍歷一邊,更新所有的點。
下面模擬一下:
我們以1為源點,來求所有點到一號點的最短路徑。
先建立一個dis數組,dis[i]表示第i號點到源點(1號點)的估計值,你可能會問為什么是估計值,因為這個估計值會不斷更新,更新到一定次數就變成答案了,這個我們一會再說。
然后我們在建立一個臨界矩陣,叫做:map,map[i][j]=v表示從i到j這條邊的權值是v。
dis初始值除了源點本身都是無窮大。源點本身都是0.
先從1號點開始。一號點,map[1][2]=5,一號點離2號點是5,比無窮大要小,所以dis[2]從無窮大變成了5。順便,我們用minn記錄距離1號點最短的點,留着以后會用。
dis[0,5,∞,∞,∞]。minn=2。
然后搜到3號點,map[1][3]=8,距離是8,比原來的dis[3]的∞小,於是dis[3]=8。但是8比dis[2]的5要大,所以minn不更新。
dis[0,5,8,∞,∞]
接着分別搜索4,5號點,發現map[1][4],map[1][5]都是∞,所以就不更新。
現在,dis數組所呈現的明顯不是最終答案,因為我們才更新一遍,現在我們開始第二次更新,第二次更新以什么為開始呢?就是以上一次我們存下來的,minn,相當於把2當源點,求所有點到它的最短路,加上它到真正的源點(1號點)的距離,就是我們要求的最短路。
從2號點開始,搜索3號點,map[2][3]=1,原本dis[3]=8,發現dis[2]+map[2][3]=5+1=6<dis[3](8)所以更新dis[3]為6,minn=3
dis[0,5,6,∞,∞] minn=3.
然后搜索4號點,map[2][4]=3,原本dis[4]=∞,所以,dis[2]+map[2][4]=5+3=8<dis[4](∞)所以更新dis[4]=8,因為map[2][4]=3,3>1,minn不更新。
dis[0,5,6,8,∞] minn=3.
接着搜索5號點,map[2][5]=2,5+2=7,7<∞,dis[5]=7minn不變。
dis[0,5,6,8,7]
二號點搜完,因為minn是3,繼續搜索3號點。
三號點還是按照二號點的方法搜索,發現沒有可以更新的,然后搜索四號。
四號搜5號點,發現8+7>5+2,所以依然不更新,然后跳出循環。
現在的估計值就全部為確定值了:
dis[0,5,6,8,7]
這就是每個點到源點一號點的距離,我們來看一下代碼:
#include <iostream> #include <algorithm> #include <cmath> #include <cstdio> #include <cstring> #include <cstdlib> using namespace std; int map[110][110];//這就是map數組,存儲圖 int dis[10010];//dis數組,存儲估計值 int book[10010];//book[i]代表這個點有沒有被當做源點去搜索過,1為有,0為沒有。這樣就不會重復搜索了。 int n,m; void dijkstra(int u)//主函數,參數是源點編號 { memset(dis,88,sizeof(dis));//把dis數組附最大值(88不是十進制的88,其實很大) int start=u;//先從源點搜索 book[start]=1;//標記源點已經搜索過 for(int i=1;i<=n;i++) { dis[i]=min(dis[i],map[start][i]);//先更新一遍 } for(int i=1;i<=n-1;i++) { int minn=9999999;//謝評論區,改正一下:這里的minn不是題解上的minn,這代表的是最近點到源點的距離,start才代表最近的點、 for(int j=1;j<=n;j++) if(book[j]==0 && minn>dis[j]) { minn=dis[j]; start=j;//找到離源點最近的點,然后把編號記錄下來,用於搜索。 } book[start]=1; for(int j=1;j<=n;j++) dis[j]=min(dis[j],dis[start]+map[start][j]);//以新的點來更新dis。 } } int main() { cin>>n>>m; memset(map,88,sizeof(map)); for(int i=1;i<=m;i++) { int a,b,c; cin>>a>>b>>c; map[a][b]=c; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i==j) map[i][j]=0; dijkstra(1);//以1為源點。 for(int i=1;i<=n;i++) cout<<dis[i]<<" "; }
這就是用鄰接矩陣實現dijkstra,但是這個算法有一個壞處,就是出現負權邊,這個算法就炸了,要解決負權邊,我以后會給大家帶來Bell man ford(SPFA)
這個算法的復雜度是O(n²),空間復雜度也是n平方,如果用鄰接表來實現,最差情況,時間復雜度是O(n*m)似乎比n²要大一些,但是空間復雜度會從n平方變成m,少了很多,現在我呈上鄰接表的代碼。
#include <iostream> #include <algorithm> #include <cmath> #include <cstdio> #include <cstring> #include <cstdlib> using namespace std; int value[10010],to[10010],next[10010]; int head[10010],total; int book[10010]; int dis[10010]; int n,m; void adl(int a,int b,int c) { total++; to[total]=b; value[total]=c; next[total]=head[a]; head[a]=total; } void dijkstra(int u) { memset(dis,88,sizeof(dis)); memset(book,0,sizeof(book)); dis[u]=0; for(int i=1;i<=n;i++) { int start=-1; for(int j=1;j<=n;j++) if(book[j]==0 && (dis[start]>dis[j] || start==-1)) start=j; book[start]=1; for(int e=head[start];e;e=next[e]) dis[to[e]]=min(dis[to[e]],dis[start]+value[e]); } } int main() { cin>>n>>m; for(int i=1;i<=m;i++) { int a,b,c; cin>>a>>b>>c; adl(a,b,c); } dijkstra(1); for(int i=1;i<=n;i++) cout<<dis[i]<<" "; }
一年多了,身為一個OIer,經歷了太多。
當年那么畏懼的Dijkstra、鄰接表,現在已經是信手拈來。
那個暑假,因為Djkstra名字的朗朗上口,講自己名字改為了Dijkstra,但是逐漸因為SPFA的可處理負權邊,也將Dijkstra,淡忘。
如今突然想起,加入了堆優化,有人說:一道題如果邊權沒有負數,那么一定是在卡SPFA。這時候就用到了堆優化的Dijkstra。
一年前提到,朴素的Dijkstra時間復雜度是n^2,被SPFA的m*常數吊打,但是,經過堆優化,Dijkstra的時間復雜度能達到nlogn,如果這個圖特別稠密的話,也就是m特別大(比如完全圖就是n^2),那么nlogn是要小於m的,這就用到了Dijkstra
首先堆優化怎么優化?觀察上面的代碼,每次循環中都再嵌套一個循環求dis值最小的點。這里,我們可以用一個優先隊列,每當搜索到一個新點,扔到優先隊列里面,這樣每次就取隊首的絕對是最優值。這樣可以省去for循環。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> #include <algorithm> #include <queue> #define in(a) a=read() #define REP(i,k,n) for(long long i=k;i<=n;i++) #define MAXN 10010 using namespace std; typedef pair<long long,long long> P; inline long long read(){ long long x=0,t=1,c; while(!isdigit(c=getchar())) if(c=='-') t=-1; while(isdigit(c)) x=x*10+c-'0',c=getchar(); return x*t; } long long n,m,s; long long total=0,head[MAXN],nxt[MAXN<<10],to[MAXN<<10],val[MAXN<<10]; long long dis[MAXN],vis[MAXN]; priority_queue <P, vector<P>,greater<P> > Q;//優先隊列優化 inline void adl(long long a,long long b,long long c){ total++; to[total]=b; val[total]=c; nxt[total]=head[a]; head[a]=total; return ; } inline void Dijkstra(){ REP(i,1,n) dis[i]=2147483647; dis[s]=0; Q.push(P(0,s)); while(!Q.empty()){ long long u=Q.top().second;//取出dis最小的點 Q.pop();//彈出 if(vis[u]) continue; vis[u]=1; for(long long e=head[u];e;e=nxt[e]) if(dis[to[e]]>dis[u]+val[e]){ dis[to[e]]=dis[u]+val[e]; Q.push(P(dis[to[e]],to[e]));//插入 } } return ; } int main(){ in(n),in(m),in(s); long long a,b,c; REP(i,1,m) in(a),in(b),in(c),adl(a,b,c); Dijkstra(); REP(i,1,n) printf("%lld ",dis[i]); }