本來我是想把這兩個算法分開寫描述的,但是SPFA其實就是Dijkstra的稀疏圖優化,所以其實代碼差不多,所以就放在一起寫了。
因為SPFA是Dijkstra的優化,所以我想來講講Dijkstra。
什么是Dijkstra
Dijkstra是一種求單源最短路的基礎算法,時間復雜度在不加堆優化的情況下是o(n^2)的,加了堆優化就能簡化到o(nlogn),而且算法穩定性很強(從這點上來說比SPFA好多了,具體怎么好下面再講),基礎思路如下:
首先,把所有點到源的距離設為最大,然后把源加入隊列,接着每次對隊列首做這種操作:用鄰接表找到從這個點能鏈到的所有所有點,接着要按大小順序找當前距離和當前隊首最近的點(這個按大小順序就是可以用堆優化的地方),做松弛操作,松弛成功,將這個點入隊,否則按大小順序找下一條邊,然后重復做松弛操作,直到當前點所有能鏈到的邊松弛過,彈出當前點,重復以上操作,直到隊列里沒有其他元素。結束程序,輸出單源到某點距離。
加了堆優化之后,有如此6的復雜度的Dijkstra,一般來說就是穩定單源最短路的最佳程序,但是在全圖有負環的時候,Dijkstra算法就顯得很無力了,在這時,我們就要用SPFA這種神奇的亂搞算法了。
什么是SPFA
SPFA算法全稱是Shortest Path Faster Algorithm算法,人家都叫“Faster”了,那肯定有過人之處,基礎思路如下:
首先,還是把所有點到源的距離設為最大,把源加入隊列,接着每次對隊首做這種操作:用鄰接表找到從這個點能鏈到的所有所有點,然后對每條邊做松弛操作,只要松弛成功,我們就判斷當前松弛成功的點是否在隊列,如果不在,就入隊,否則就只是做松弛,然后彈出當前點,對下一點再次做以上操作,直到隊列為空。
當然,和Dijkstra相比,SPFA看起來相當的亂搞,但是,對於絕大多數(稀疏)圖來說,加了SLF和LLL兩種優化的SPFA肯定比Dijkstra快得多(SLF和LLL就是兩種決策優化,前者對於最值優化,后者是均值優化)。
順帶附上一道codevs1021的SPFA題目與代碼
麥克找了個新女朋友,瑪麗卡對他非常惱火並伺機報復。
因為她和他們不住在同一個城市,因此她開始准備她的長途旅行。
在這個國家中每兩個城市之間最多只有一條路相通,並且我們知道從一個城市到另一個城市路上所需花費的時間。
麥克在車中無意中聽到有一條路正在維修,並且那兒正堵車,但沒聽清楚到底是哪一條路。無論哪一條路正在維修,從瑪麗卡所在的城市都能到達麥克所在的城市。
瑪麗卡將只從不堵車的路上通過,並且她將按最短路線行車。麥克希望知道在最糟糕的情況下瑪麗卡到達他所在的城市需要多長時間,這樣他就能保證他的女朋友離開該城市足夠遠。
編寫程序,幫助麥克找出瑪麗卡按最短路線通過不堵車道路到達他所在城市所需的最長時間(用分鍾表示)。
第一行有兩個用空格隔開的數N和M,分別表示城市的數量以及城市間道路的數量。1≤N≤1000,1≤M≤N*(N-1)/2。城市用數字1至N標識,麥克在城市1中,瑪麗卡在城市N中。
接下來的M行中每行包含三個用空格隔開的數A,B和V。其中1≤A,B≤N,1≤V≤1000。這些數字表示在A和城市B中間有一條雙行道,並且在V分鍾內是就能通過。
輸出文件的第一行中寫出用分鍾表示的最長時間,在這段時間中,無論哪條路在堵車,瑪麗卡應該能夠到達麥克處,如果少於這個時間的話,則必定存在一條路,該條路一旦堵車,瑪麗卡就不能夠趕到麥克處。
5 7
1 2 8
1 4 10
2 3 9
2 4 10
2 5 1
3 4 7
3 5 10
27
題解如下
這道題關鍵在於最優解可能會無法連通,所以我們求出最優解之后,要枚舉最優解中哪條路會斷,取其中最壞情況(題目所需),得答案就好,因為算次優解一定不能使數據再包括最優解的某條邊,不然就和最優解一樣了,所以以上方法是正確的。
貼出代碼
1 #include<stdio.h> 2 struct shit{ 3 int aim; 4 int get; 5 int next; 6 int lon; 7 }e[3010000]; 8 int max(int x,int y) 9 { 10 return x>y?x:y; 11 } 12 int pre[3010000]; 13 int d[1010000],a,b,n,m,num,star,ass,quq[60100000],point,head[1010000],ans; 14 bool f[10101000]; 15 void fuck(int x,int y,int s)//建邊 16 { 17 e[++point].aim=x; 18 e[point].lon=s; 19 e[point].get=y; 20 e[point].next=head[y]; 21 head[y]=point; 22 } 23 int main() 24 { 25 scanf("%d%d",&n,&m); 26 for(int i=1;i<=m;i++) 27 { 28 scanf("%d%d%d",&a,&b,&num); 29 fuck(a,b,num); 30 fuck(b,a,num); 31 } 32 quq[++star]=1; 33 ass=1; 34 f[1]=true; 35 for(int i=2;i<=n;i++) 36 d[i]=214748364; 37 while(star<=ass) 38 { 39 for(int k=head[quq[star]];k!=0;k=e[k].next) 40 if(d[quq[star]]+e[k].lon<d[e[k].aim]) 41 { 42 d[e[k].aim]=d[quq[star]]+e[k].lon; 43 pre[e[k].aim]=k;//第一次的SPFA要記錄路徑(點的位置) 44 if(!f[e[k].aim]) 45 { 46 quq[++ass]=e[k].aim; 47 f[e[k].aim]=true; 48 } 49 } 50 f[quq[star++]]=false; 51 } 52 ans=max(d[n],ans); 53 int q=n; 54 while(q!=0) 55 { 56 a=e[pre[q]].lon; 57 e[pre[q]].lon=214748364;//直接設置當前邊數值極大(即設置為不連通) 58 if(pre[q]%2==1)//根據讀入邊的方法來雙向刪邊 59 e[pre[q]+1].lon=214748364; 60 else 61 e[pre[q]-1].lon=214748364; 62 star=1; 63 for(int i=1;i<=ass;i++) 64 quq[i]=0; 65 quq[star]=1; 66 ass=1; 67 f[1]=true; 68 for(int i=2;i<=n;i++) 69 { 70 d[i]=214748364; 71 f[i]=false; 72 } 73 while(star<=ass) 74 { 75 for(int k=head[quq[star]];k!=0;k=e[k].next) 76 if(d[quq[star]]+e[k].lon<d[e[k].aim]) 77 { 78 d[e[k].aim]=d[quq[star]]+e[k].lon; 79 if(!f[e[k].aim]) 80 { 81 quq[++ass]=e[k].aim; 82 f[e[k].aim]=true; 83 } 84 } 85 f[quq[star++]]=false; 86 } 87 if(d[n]!=214748364)ans=max(d[n],ans);//如果數值就為我們設置的最大,即切去當前邊的圖無法連通 88 e[pre[q]].lon=a; 89 if(pre[q]%2==1)//復原切去的邊 90 e[pre[q]+1].lon=a; 91 else 92 e[pre[q]-1].lon=a; 93 q=e[pre[q]].get; 94 } 95 printf("%d",ans); 96 return 0; 97 }
PS:沒有加LLL和SLF的主要原因是我還不會打→_→
