1. 程式人生 > >藍橋杯-最短路 (SPFA算法學習)

藍橋杯-最短路 (SPFA算法學習)

can ios AC 定義 頂點 不能 最短距離 尾指針 內容

SPFA算法主要用來解決存在負邊權的單源最短路情況(但不能有負環!!!)一個簡單的方法判斷是否有沒有負環可以通過判斷是否有一個節點是否頻繁進出隊列。

以下內容轉自https://blog.csdn.net/xunalove/article/details/70045815

求單源最短路的SPFA算法的全稱是:Shortest Path Faster Algorithm。
SPFA算法是西南交通大學段凡丁於1994年發表的。
從名字我們就可以看出,這種算法在效率上一定有過人之處。
很多時候,給定的圖存在負權邊,這時類似Dijkstra等算法便沒有了用武之地,而Bellman-Ford算法的復雜度又過高,SPFA算法便派上用場了。有人稱spfa算法是最短路的萬能算法。

簡潔起見,我們約定有向加權圖G不存在負權回路,即最短路徑一定存在。當然,我們可以在執行該算法前做一次拓撲排序,以判斷是否存在負權回路。
我們用數組dis記錄每個結點的最短路徑估計值,可以用鄰接矩陣或鄰接表來存儲圖G,推薦使用鄰接表。

spfa的算法思想(動態逼近法):

設立一個先進先出的隊列q用來保存待優化的結點,優化時每次取出隊首結點u,並且用u點當前的最短路徑估計值對離開u點所指向的結點v進行松弛操作,如果v點的最短路徑估計值有所調整,且v點不在當前的隊列中,就將v點放入隊尾。這樣不斷從隊列中取出結點來進行松弛操作,直至隊列空為止。
松弛操作的原理是著名的定理:“三角形兩邊之和大於第三邊”,在信息學中我們叫它三角不等式。所謂對結點i,j進行松弛,就是判定是否dis[j]>dis[i]+w[i,j],如果該式成立則將dis[j]減小到dis[i]+w[i,j],否則不動。
下面舉一個實例來說明SFFA算法是怎樣進行的:

技術分享圖片
技術分享圖片



和廣搜bfs的區別:
SPFA 在形式上和廣度(寬度)優先搜索非常類似,不同的是bfs中一個點出了隊列就不可能重新進入隊列,但是SPFA中一個點可能在出隊列之後再次被放入隊列,也就是一個點改進過其它的點之後,過了一段時間可能本身被改進(重新入隊),於是再次用來改進其它的點,這樣反復叠代下去。

算法的描述:

void  spfa(s);  //求單源點s到其它各頂點的最短距離
    for i=1 to n do { dis[i]=∞; vis[i]=false; }   //初始化每點到s的距離,不在隊列
    dis[s]=0;  //將dis[源點]設為0
    vis[s]=true; //源點s入隊列
    head=0; tail=1; q[tail]=s; //源點s入隊, 頭尾指針賦初值
    while head<tail do {
       head+1;  //隊首出隊
       v=q[head];  //隊首結點v
       vis[v]=false;  //釋放對v的標記,可以重新入隊
for 每條邊(v,i) //對於與隊首v相連的每一條邊 if (dis[i]>dis[v]+a[v][i]) //如果不滿足三角形性質 dis[i] = dis[v] + a[v][i] //松弛dis[i] if (vis[i]=false) {tail+1; q[tail]=i; vis[i]=true;} //不在隊列,則加入隊列 }

最短路徑本身怎麽輸出?
在一個圖中,我們僅僅知道結點A到結點E的最短路徑長度,有時候意義不大。這個圖如果是地圖的模型的話,在算出最短路徑長度後,我們總要說明“怎麽走”才算真正解決了問題。如何在計算過程中記錄下來最短路徑是怎麽走的,並在最後將它輸出呢?
我們定義一個path[]數組,path[i]表示源點s到i的最短路程中,結點i之前的結點的編號(父結點),我們在借助結點u對結點v松弛的同時,標記下path[v]=u,記錄的工作就完成了。
如何輸出呢?我們記錄的是每個點前面的點是什麽,輸出卻要從最前面到後面輸出,這很好辦,遞歸就可以了。

模板題:

藍橋杯- 算法訓練 最短路

思路:spfa算法,因為題目告訴存在負邊,不存在負環,而節點數n較大,因此不能使用Dijkstra算法和Floyd 算法。

這題因為數組不能開太大,所以鄰接表用vector 存一個結構體變量。

 1 #include<iostream>
 2 #include<string.h>
 3 #include<string>
 4 #include<algorithm>
 5 #include<bits/stdc++.h>
 6 #define INF 1e9
 7 using namespace std;
 8 struct Node{
 9     int  num;
10     int load=INF;
11 };
12 vector < Node > v[20000+50];
13 //int load[20050][20050];
14 int len[20050];
15 bool vis[20050];
16 void SPFA(){
17     queue < int > q;
18     q.push(1);
19     memset(vis,0,sizeof(vis));
20     vis[1]=1;
21     while(!q.empty()){
22         int now=q.front();
23         q.pop();
24         vis[now]=0;
25         for(int i=0;i<v[now].size();i++){
26             int tmp=v[now][i].num;
27             int link=v[now][i].load;
28             if(len[tmp]>link+len[now]){
29                 len[tmp]=link+len[now];
30                 if(vis[tmp]==0){
31                     q.push(tmp);
32                     vis[tmp]=1;
33                 }
34             }
35         }
36     }
37     return ;
38 }
39 int main(){
40     for(int i=0;i<20030;i++){
41         len[i]=INF;
42     }
43     len[1]=0;
44     int n,m;
45     cin>>n>>m;
46     for(int i=0;i<m;i++){
47         int x,y,l;
48         Node tmp;
49         scanf("%d%d%d",&x,&y,&l);
50         tmp.num=y;
51         tmp.load=l;
52         v[x].push_back(tmp);
53 
54     }
55     SPFA();
56     for(int i=2;i<=n;i++){
57         cout<<len[i]<<endl;
58     }
59     return 0;
60 }

藍橋杯-最短路 (SPFA算法學習)