最短路径之spfa
引入1:單源最短路
問:求帶權有向圖上一個源點到其他點的最短路徑距離
如果沒有非負邊權,我們自然可以想到dij。但是如果有負邊權呢?這時候就要用SPFA算法求解。
原理&講解
用dis數組記錄源點到有向圖上任意一點距離,其中源點到自身距離為0,到其他點距離為INF。將源點入隊,并重復以下步驟:
- 隊首x出隊
- 遍歷所有以隊首為起點的有向邊(x,i),若dis[x]+w(x,i)<dis[i],則更新dis[i]
- 如果點i不在隊列中,則i入隊
- 若隊列為空,跳出循環;否則執行1
實際上我們可以將其理解為bfs
如果圖是隨機生成的,時間復雜度為 O(KM) (K可以認為是個常數,m為邊數,n為點數)
但是實際上SPFA的算法復雜度是 O(NM) ,可以構造出卡SPFA的數據,讓SPFA超時。
在NOI 2018的第一天第一題中,出題人卡了SPFA算法,導致100分變成60分,所以在沒有負環、單純求最短路徑,不建議使用SPFA算法,而是用Dijkstra算法。
在初一我們學到一條三角形中的性質,即同一三角形內兩邊之和大于第三邊。而最短路中如u->v的最短路它是小于等于其它任意路徑的,這使我們容易yy到三角形。也就是說,我們實際上每次都是在判斷這條路徑符不符合三角形不等式,若不符合,我們就將原先的路徑松弛為現在的路徑,使得現在的路徑滿足三角形不等式。但是為什么松弛后要將終點入隊呢?SPFA的過程是BFS,它是不停擴展節點的。而當我們更新了這一條路徑,那么可能會出現基于這一條路徑的新路,我們需要判斷原路與新路是否滿足三角形不等式。
模擬&代碼
我們可以手推這張圖模擬一下~
我們以1為源點,初始化:dis[源點]=0,其他為正無窮,并將源點入隊。
隊首1出隊,并枚舉它的出邊1->2,1->3。由dis[1]+w(1,2)=1<dis[2]=INF,dis[1]+w(1,3)=6<dis[3]=INF得dis[2]=dis[1]+w(1,2)=1,dis[3]=dis[1]+w(1,3)=6,并將2,3入隊。
隊首2出隊,枚舉它的出邊2->3,2->4,2->5。都不滿足三角形不等式,所以松弛它們。并將3,4,5入隊,但由于3已在隊內,所以不管。
隊首3出隊,沒有能松弛的邊,直接略過。
此時隊內剩下4,5,由于這兩點沒有出邊,所以在此不枚舉。
手繪勿噴
下面是帶注釋代碼:
#include<iostream> #include<vector> #include<algorithm> #include<cstring> #include<string> #include<cstdio> #include<cstdlib> #include<queue> #define N 110000 #define INF 0x3f3f3f3f using namespace std;int n,m,a,b,c,vis[N],dis[N];struct node {int d,w; };//定義一個結構體來存儲每個入度點以及對應邊的權值 //比如邊u->v,權值為w,node結構體存儲的就是v以及w。vector<node>v[N];void spfa(int u);int main() {//對于N非常大但是M很小的這種稀疏圖來說,用鄰接矩陣N*N是存不下的。鄰接矩陣是將所有的點都存儲下來了,然而//對于稀疏圖來說,有很多點是沒有用到的,把這些點也存儲下來的話就會很浪費空間。可以用鄰接表來存儲,這里借助vector來實現鄰接表的操作。//用鄰接表存儲時候,只存儲有用的點,對于沒有用的點不存儲,實現空間的優化。cin>>n>>m;for(int i=0; i<=n; i++)v[i].clear();//將vecort數組清空for(int i=1; i<=m; i++) //用vector存儲鄰接表{node nd;scanf("%d%d%d",&a,&b,&c);nd.d=b,nd.w=c;//將入度的點和權值賦值給結構體v[a].push_back(nd);//將每一個從a出發能直接到達的點都壓到下標為a的vector數組中,以后遍歷從a能到達的點就可以直接遍歷v[a]// nd.d=a,nd.w=c;//無向圖的雙向存邊// v[b].push_back(nd);}spfa(1);if(dis[n]!=INF)printf("%d\n",dis[n]);elseprintf("impossible");return 0; } void spfa(int u){memset(vis,1,sizeof(vis));memset(dis,0x3f,sizeof(dis));dis[u]=0;queue<int> q;q.push(u);vis[u]=false;while (!q.empty()) {int x=q.front();q.pop();vis[x]=true;vector<node> s=v[x];for (int i = 0; i < s.size(); ++i) {int v=s[i].d;if(dis[x]+s[i].w<dis[v]){dis[v]=dis[x]+s[i].w;if(vis[v]){q.push(v);vis[v]=false;}}}} }引入2:判正(負)環
spfa算法還可以在有向圖內判正環負環,我們可以使用DFS/BFS版SPFA。注意,判負環跑最短路,判正環跑最長路。
#include<iostream> #include<vector> #include<algorithm> #include<cstring> #include<string> #include<cstdio> #include<cstdlib> #include<queue> #define N 110000 #define INF 0x3f3f3f3f using namespace std;int n,m,a,b,c,instack[N],dis[N],flag;struct node {int d,w; };//定義一個結構體來存儲每個入度點以及對應邊的權值 //比如邊u->v,權值為w,node結構體存儲的就是v以及w。vector<node>v[N];void spfa(int u);int main() {//對于N非常大但是M很小的這種稀疏圖來說,用鄰接矩陣N*N是存不下的。鄰接矩陣是將所有的點都存儲下來了,然而//對于稀疏圖來說,有很多點是沒有用到的,把這些點也存儲下來的話就會很浪費空間。可以用鄰接表來存儲,這里借助vector來實現鄰接表的操作。//用鄰接表存儲時候,只存儲有用的點,對于沒有用的點不存儲,實現空間的優化。cin>>n>>m;for(int i=0; i<=n; i++)v[i].clear();//將vecort數組清空for(int i=1; i<=m; i++) //用vector存儲鄰接表{node nd;scanf("%d%d%d",&a,&b,&c);nd.d=b,nd.w=c;//將入度的點和權值賦值給結構體v[a].push_back(nd);//將每一個從a出發能直接到達的點都壓到下標為a的vector數組中,以后遍歷從a能到達的點就可以直接遍歷v[a]// nd.d=a,nd.w=c;//無向圖的雙向存邊// v[b].push_back(nd);}memset(instack,0,sizeof(instack));memset(dis,0,sizeof(dis));flag=0;for(int i=1;i<=n;i++){spfa(i);if(flag)break;}if(flag)printf("Yes");else printf("No");return 0; } void spfa(int u){if(instack[u]){flag=1;return;}instack[u]=true;vector<node> s=v[u];for (int i = 0; i < s.size(); ++i) {if(dis[u]+s[i].w<dis[s[i].d]){dis[s[i].d]=dis[u]+s[i].w;spfa(s[i].d);if(flag)return;}}instack[u]=false; }
?
轉載于:https://www.cnblogs.com/clarencezzh/p/10382939.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
- 上一篇: S/4HANA生产订单增强WORKORD
- 下一篇: 冬奥会纪念币一盒几卷