1. 程式人生 > >【ACM】spfa演算法(適用於存在負權)

【ACM】spfa演算法(適用於存在負權)

一:演算法描述
求單源最短路的SPFA演算法,是一種可以處理負權邊的演算法。對於存在負權邊,迪傑斯特拉演算法不能使用,但是bellman-ford時間複雜度較高。
簡潔起見,我們約定有向加權圖G不存在負權迴路,即最短路徑一定存在。當然,我們可以在執行該演算法前做一次拓撲排序,以判斷是否存在負權迴路。

二:演算法基本步驟
幾乎所有的最短路徑演算法都是以下兩個步驟:
①初始化
②鬆弛操作

初始化:
dis陣列全部賦值為INF,vis陣列標記是否在佇列中,一開始佇列中沒有結點,所有vis陣列元素都設定為false。
佇列+鬆弛操作:
讀取隊頭元素,出隊並且修改vis陣列值為false;將與點u相連的所有點v進行鬆弛操作,如果能更新估計值(即令d[v]變小),那麼就更新,另外,如果點v沒有在佇列中,那麼要將點v入隊(記得標記),如果已經在佇列中了,那麼就不用入隊。(因為被鬆弛過的結點可能會影響到其他結點的dis值,所以要繼續入隊)

三:判斷
判斷有無負環:
如果某個點進入佇列的次數超過N次則存在負環(SPFA無法處理帶負環的圖)

四:程式碼

#include<cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 2005;
int nodeNum , edgeNum;
int dis[N]; // d[i]表示源點s到i的距離
int c[N]; // 統計每一個結點入隊的次數
bool vis[N];
const
int INF = 0x3f3f3f3f; bool loop; //判斷是否是迴路 int map[nodeNum][nodeNum]; void spfa(int start) { //建立佇列,初始化dis陣列和vis陣列,把源結點加入到佇列中 queue<int >q; memset(vis,false,sizeof(vis)); for(int i=1;i<=nodeNum;i++) dis[i]=INF; dis[start]=0; q.push(start); vis[start]=true; //在佇列頭部取點,然後修改相關資料,並對該邊所連線的鄰接點進行鬆弛操作
//判斷相鄰的結點vis[],是否在佇列中,要是不在,加入佇列 while(!q.empty()) { int temp = q.front(); q.pop(); vis[temp] = false; for(int i=1;i<=nodeNum;i++) { if(dis[temp] + map[temp][i] < dis[i]) { dis[i] = dis[temp] + map[temp][i]; if(!vis[i]) { q.push(i); vis[i] = true; } } } } }

五:優化策略

1.SLF策略:
若要加入的結點是j,隊首結點是i,如果dis[j] < dis[i],則將j插入到隊首;否則,插入到隊尾。

2.LLL策略
設隊首元素為i,佇列中所有dis的平均值是x,若dis[i] > x,則將i插入到隊尾,查詢下一個元素知道找到一個dis[i]<=x,則出隊進行鬆弛操作。