最短路算法總結---單源最短路徑(SSSP)


眾所周知,最短路算法在比賽中占有相當部分的分值

在大多數情況下,甚至使用並非最佳的算法也可以的得到相當大部分的分數。

以下選自書中核心內容,是競賽生要熟練掌握且清晰理解的幾種最基本算法。

(全部化為有向圖做,雙向邊就化為兩條單向邊,恩,就這樣操作)

以下所有討論不考慮環,全部INF處理,請悉知。


 

 

一.Dijkstra算法(貪心)(O(n^2))(效率一般,但相當可做)(邊權非負,否則。。。qwq)

1.dist[1]=0 , 其余 dist = INF

->2.找出一個未標記,dist[x]最小的節點x,標記x。

->3.掃描節點x的所有出邊(x,y,z),if (dist[y]>dist[x]+z ) dist[y]=dist[x]+z (這是這個最基本的算法的核心語句,具體可想象三角形三邊關系)。

(注意這里邊權為負數的話,那么我們2中最先選擇出的起點就不一定最小了,那萬一以后跑出來個負邊,全局都會受影響,那咱還貪個啥心,還跑個啥Dijkstra)

4.重復2-3直到全部點都被標記(233要是所有點走的線路都試過了,沒有答案你來打我啊)

 

二.Bellman-Ford算法(O(nm))(我不咋用,但這個是SPFA的原型)

->1.掃描所有邊(x,y,z),if (dist[y]>dist[x]+z)dist[y]=dist[x]+z (我沒看錯吧?我把上文抄了下來?沒錯,幾種算法的基本套路是一樣的)

(但要注意,這里不同的是這里Bell-ford的2並非像Dijkstra那樣要求對所有點掃描,而僅僅是某一部分。)

(為神魔呢?這是樣做有道理的。證明:若一張有向圖的一條邊滿足三角形不等式,即dist[y]<=dist[x]+z,那么所有這樣的邊連起來的一條路肯定最短啦,干嘛還要把所有邊都跑一邊呢,這樣就剪掉很多不必要去掃的邊)

2.重復,直到1那家伙掃完 (即基於三角不等式的關系下不再發生任何更新)。

那么,就這樣愉快而簡單地結束啦233。(松弛,向最優)

 

注意到這里的不同:Dijkstra側重基於點的掃描(直到全部點標記完),而Bell-ford側重基於邊的關系(直到不再發生更新),這也是兩者設計的不同。(粗鄙見解)

但是這就結束了嗎?ccf的樣例可不會山吧干修(誤),我們來把Bellman-Ford優化得到一個更低時間復雜度的版本。

怎么優化呢,加一個隊列吧,讓這個隊列只有待擴展的點,每次都是滿足三角不等式才入隊,避免了Bellman-ford去掃那些不需要的邊。(冗余掃描)

這樣在稀疏的有向圖上會更快,只在密圖或特殊圖上退化為朴素的Bellmand-ford。

 

三.SPFA算法(O(km))

1.建立一個隊列,最初只含起點。

->2.取出隊頭x,掃描其幾個出邊(x,y,z),If (dist[y]>dist[x]+z ) dist[y]=dist[x]+z.把y入隊(已在隊中就不用了)(其實dist一直在記錄起點到該點的距離,而我們的更新,就是在走不同路徑時記錄下該點到起點更近的距離)

3.重復2直到隊列空。

(怎么樣?基於隊列操作的Bellman-ford是不是一下子省去很多不用掃的內容啊)


 

好了,到這里關於單源最短路(SSSP)的算法都復習完了。

相信是個像我這種笨蛋也思路清晰了。

那么,我們來敲下熟悉到吐了的板子吧233

敲板子是一件愉快的事(因為不用動腦子啊,理解題意后胡亂選個合適的算法,敲敲板子,再針對題中具體情況小小地改動一下,做做特殊處理,然后這種語文題就交給板子去跑啦qwq。大多數情況下還是可以直接拿到不錯的部分分數,相信再調調細節聰明的你就可以ac啦)。

 來自lrd書上的幾個板子

 

 

熟悉一下鄰接表  空間復雜度O(n+m)

ver(V---點)記錄每條邊的終點,edge(E---邊)記錄對應每條邊的邊權。

head記錄從每個節點出發第一條邊在ver和edge數組中儲存的位置。  

next是下一條邊在ver和edge中的位置。

 1 void add(int x,int y,int z){
 2 ver[++tot]=y,edge[tot]=z;
 3 next[tot]=head[x],head[x]=tot;
 4 }
 5 //加邊
 6 
 7 for(int i=head[x];i;i=next[i]){
 8 int y=ver[i],z=edge[i];
 9 。。。。。。
10 }
11 //訪問從x出發的邊
View Code

(LXL不對該段代碼負責qwq)

 

Dijkstra板子(用於稠密圖更快)

 1 int a[3010][3010],d[3010],n,m;
 2 bool v[3010];
 3 
 4 void dijkstra(){
 5 memset(d,0x3f,sizeof(d));//dist數組
 6 memset(v,o,sizeof(v));//節點標記
 7 d[1]=0;//漏了這句可是跑不了的哦。
 8 for(int i=1;i<n;i++){
 9 int x=0;//
10 for(int j=1;j<=n;j++) if(!v[j]&&(x==0||d[j]<d[x])) x=j;//找剩余未掃描點中更近的點
11 v[x]=1;//
12 for(int y=1;y<=n;y++) d[y]=min(d[y],d[x]+a[x][y]);//有沒有找回動態規划的感覺,沒錯,是初戀的感覺。
13 //當然,這里只是更新下每個 <點到源> 的最小距離。
14 }
15 //重復n-1次掃描全部節點
16 }
17 
18 int main(){
19 cin>>n>>m;
20 memset(a,0x3f,sizeof(a));
21 for(int i=1;i<=n;i++) a[i][i]=0;
22 for(int i=1;i<=m;i++){
23 int x,y,z;
24 scanf("%d%d%d",&x,&y,&z);
25 a[x][y]=min(a[x][y],z);
26 //在建圖時已經開始預處理下
27 }
28 dijkstra();
29 for(int i=1;i<=n;i++)
30 printf("%d\n",d[i]);//記錄了所有最終態的距離,每個點到源的最短距。
31 return 0;
32 }
朴素寫法

 

 1 const int N=100010,M=1000010;
 2 int head[N],ver[N],edge[M],Next[M],d[N];
 3 bool v[N];
 4 int n,m,tot;
 5 priority_queue<pair<int,int>> q;
 6 void add(int x,int y,int z){
 7    ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
 8 }
 9 void dijkstra();
10 int main(){
11    cin>>n>>m;
12    for(int i=1;i<=m;i++){
13    int x,y,z;
14    scanf("%d%d%d",&x,&y,&z);
15    add(x,y,z);
16    }
17    dijkstra();
18    for(int i=1;i<=n;i++){
19       printf("%d\n",d[i]);
20    }
21 return 0;
22 }
23 void dijkstra(){
24    memset(d,0x3f,sizeof(d));
25    memset(v,0,sizeof(v));
26    d[1]=0;
27    q.push(make_pair(0,1));
28    while(q.size()){
29       int x=q.top().second;q.pop();
30       if(v[x]) continue;
31       v[x]=1;
32       //注意這里的運行原理是找到當前的最短距離(貪心),繼續更新
33       for(int i=head[x];i;i=Next[i]){
34         int y=ver[i],z=edge[i];
35         if(d[y]>d[x]+z) {
36             d[y]=d[x]+z;
37             q.push(make_pair(-d[y],y));
38         }
39       } //整段在當前全局最小值的基礎上去更新其他d值
40    }//原理是用二叉堆來節省find全局最小值的時間。
41 
42 }
堆優化寫法(推薦掌握)

上面注釋寫了不少廢話(甚至描述有偏差),請直接無視。

SPFA板子(用於稀疏圖更快)

 1 const int N=100010,M=1000010;
 2 int head[N],ver[M],edge[M],Next[M],d[N];
 3 int n,m,tot;
 4 queue<int>q;
 5 bool v[N];
 6 void add(int x,int y,int z){ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;}
 7 void spfa(){
 8  memset(d,0x3f,sizeof(d));
 9  memset(v,0,sizeof(v));
10  d[1]=0;v[1]=1;
11  q.push(1);
12  while(q.size()){
13    int x=q.front();q.pop();
14    v[x]=0;
15    for(int i=head[x];i;i=Next[i]){
16       int y=ver[i],z=edge[i];
17       if(d[y]>d[x]+z){
18          d[y]=d[x]+z;
19          if(!v[y]) q.push(y),v[y]=1;
20       }
21     }
22   } 
23 }
24 int main(){
25  cin>>n>>m;
26  for(int i=1;i<=m;i++){int x,y,z;scanf("%d%d%d",&x,&y,&z);add(x,y,z);}
27  spfa();
28  for(int i=1;i<=n;i++) printf("%d\n",d[i]);
29  return 0;
30 }
SPFA

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM