算法介紹:php
SPFA(Shortest Path Faster Algorithm)是Bellman-Ford算法的一種隊列實現,減小了沒必要要的冗餘計算。ios
算法流程:算法
算法大體流程是用一個隊列來進行維護。 初始時將源加入隊列。 每次從隊列中取出一個元素,並對全部與他相鄰的點進行鬆弛,若某個相鄰的點鬆弛成功,則將其入隊。 直到隊列爲空時算法結束。數組
這個算法,簡單的說就是隊列優化的bellman-ford,利用了每一個點不會更新次數太多的特色發明的此算法優化
SPFA——Shortest Path Faster Algorithm,它能夠在O(kE)的時間複雜度內求出源點到其餘全部點的最短路徑,能夠處理負邊。SPFA的實現甚至比Dijkstra或者Bellman_Ford還要簡單:spa
設Dist表明S到I點的當前最短距離,Fa表明S到I的當前最短路徑中I點以前的一個點的編號。開始時Dist所有爲+∞,只有Dist[S]=0,Fa所有爲0。code
維護一個隊列,裏面存放全部須要進行迭代的點。初始時隊列中只有一個點S。用一個布爾數組記錄每一個點是否處在隊列中。blog
每次迭代,取出隊頭的點v,依次枚舉從v出發的邊v->u,設邊的長度爲len,判斷Dist[v]+len是否小於Dist[u],若小於則改進Dist[u],將Fa[u]記爲v,而且因爲S到u的最短距離變小了,有可能u能夠改進其它的點,因此若u不在隊列中,就將它放入隊尾。這樣一直迭代下去直到隊列變空,也就是S到全部的最短距離都肯定下來,結束算法。若一個點入隊次數超過n,則有負權環。隊列
SPFA 在形式上和寬度優先搜索很是相似,不一樣的是寬度優先搜索中一個點出了隊列就不可能從新進入隊列,可是SPFA中一個點可能在出隊列以後再次被放入隊列,也就是一個點改進過其它的點以後,過了一段時間可能自己被改進,因而再次用來改進其它的點,這樣反覆迭代下去。設一個點用來做爲迭代點對其它點進行改進的平均次數爲k,有辦法證實對於一般的狀況,k在2左右。 在實際的應用中SPFA的算法時間效率不是很穩定,爲了不最壞狀況的出現,一般使用效率更加穩定的Dijkstra算法。ip
代碼實現:
/************************************************************************* > File Name: SPFA.cpp > Author: He Xingjie > Mail: gxmshxj@163.com > Created Time: 2014年06月12日 星期四 18時24分37秒 > Description: ************************************************************************/ #include<iostream> #include<queue> #include<vector> #include<cstdio> #include<stack> using namespace std; #define MAX 50 #define INF 65535 typedef struct Edge{ int w; //邊的權值 int end; //邊的終點 }Edge; vector<Edge> e[MAX]; //動態數組模擬鄰接鏈表存儲 queue<int> que; //存儲節點 int dist[MAX], pre[MAX]; bool in[MAX]; //標識是否在隊列裏面 int cnt[MAX]; //記錄入隊次數 int V, E; //頂點數和邊數 int Init() { int st, end, weight; cin>>V>>E; for (int i=0; i < E; i++) { cin>>st>>end>>weight; Edge tmp; tmp.w = weight; tmp.end = end; e[st].push_back(tmp); //存入vector } for (int i=0; i< V; i++) { dist[i]= INF; cnt[i] = 0; in[i] = false; pre[i] = 0; } } bool SPFA(int st) { int i, v, end; dist[st] = 0; que.push(st); in[st] = true; cnt[st]++; while (!que.empty()) { v = que.front(); que.pop(); in[v] = false; for (i=0; i<e[v].size(); i++) { Edge t = e[v][i]; //取出鄰接邊 int end = t.end; //記錄與v鄰接的節點 if (dist[end] > dist[v] + t.w) //更新路徑權值,v->end的權值爲w { dist[end] = dist[v] + t.w; pre[end] = v; //記錄前一個節點 } if (!in[end]) //不在隊列裏面 { que.push(end); in[end] = true; cnt[end]++; if (cnt[end] > V) //入隊次數大於V次 { while (!que.empty()) //清空隊列 { que.pop(); } return false; } } } } return true; } void ShowPath() { int i; stack<int> st; for (i=0; i<V; i++) { st.push(i); int tmp = pre[i]; while (tmp != 0) { st.push(tmp); tmp = pre[tmp]; } cout<<"1"; while (!st.empty()) { tmp = st.top(); st.pop(); cout<<"->"<<tmp+1; } cout<<" : "<<dist[i]<<endl; } } int main() { bool ret; freopen("input.txt", "r", stdin); Init(); ret = SPFA(0); ShowPath(); if (ret) cout<<"No Negative circle"<<endl; else cout<<"Exit Negative circle"<<endl; return 0; } 2014/6/13 23:38
輸入數據:
5 9 0 1 3 2 1 4 1 4 7 1 3 1 0 2 8 3 0 2 0 4 -4 4 3 6 3 2 -5
輸出數據:
參考: