图论 —— 最短路 —— Bellman-Ford 算法与 SPFA
【概述】
Bellman-Ford算法適用于計算單源最短路徑,即:只能計算起點只有一個的情況。
其最大特點是可以處理存在負邊權(quán)的情況,但無法處理存在負權(quán)回路的情況。
其時間復(fù)雜度為:O(V*E),其中,V?是頂點數(shù),E 是邊數(shù)。
【算法分析】
Bellman Ford 算法與 Dijkstra 算法的思想相同,只不過 Dijkstra 是每次確定一個最短距離點,并用這個點去更新與之相連的其他邊,而 Ford 算法是每次更新所有的邊,從而確定一個點的最短距離
起始時,認為起點是白點(dis[1]=0),每一次都枚舉所有的邊,必然會有一些邊,連接著白點和藍點。因此每次都能用所有的白點去修改所有的藍點,每次循環(huán)也必然會有至少一個藍點變成白點。
以下圖為例
令起點為白點,即:dis[1]=0,dis[2、3、4、5]=∞
遍歷所有邊,將與白點相連的藍點變?yōu)榘c,即:dis[1]=0,dis[2]=2,dis[3]=1,dis[4]=2,dis[5] =∞
繼續(xù)向下遍歷,修改藍點為白點,即:dis[1]=0,dis[2]=2,dis[3]=1,dis[4]=2,dis[5] =4
【算法核心】
設(shè)起點為 s,dis[v] 表示從 s 到 v 的最短路徑,u[i] 和 v[i] 分別表示第 i 條邊的起點和終點,w[j] 是連接 u、v 的邊 j 的長度。
初始化:
dis[s]=0,dis[v]=0x3f3f3f3f(v≠s),即:初始化為一極大值
算法主體:
void Bellman_Ford() {for(int i=0;i<n;i++) dis[i]=INF;dis[0]=0;for(int i=1;i<=n-1;i++)//枚舉除終點外的所有點for(int j=1;j<=m;j++)//枚舉所有邊{int x=u[j];//邊j的起點int y=v[j];//邊j的終點if(dis[x]<INF)//松弛dis[y]=min(dis[y],dis[x]+w[j]);} }算法結(jié)束:dis[v] 即為 s 到 v 最短距離
【SPFA】
SPFA 實質(zhì)就是?Ford 算法加了判斷負環(huán)的隊列實現(xiàn)版本。
其利用隊列以進行 Ford 算法的過程,初始時將起點加入隊列,每次從隊列中取出一個元素,并對所有與它相鄰的點進行修改,若相鄰的點修改成功,則將其入隊,直到隊列為空時算法結(jié)束。
SPFA 時間復(fù)雜度可達:O(k*E),其中,E 是邊數(shù),k 是常數(shù),平均值是 2。
int dis[N]; bool vis[N]; void SPFA(int S) {memset(vis, false, sizeof(vis));memset(dis, INF, sizeof(dis));dis[S] = 0;queue<int> Q;Q.push(S);while (!Q.empty()) {int x = Q.front();Q.pop();vis[x] = false;for (int i = head[x]; i != -1; i = edge[i].next) {int to = edge[i].to;if (dis[to] > dis[x] + edge[i].dis) {dis[to] = dis[x] + edge[i].dis;if (!vis[to]) {vis[to] = true;Q.push(to);}}}} }【SPFA 的 SLF 優(yōu)化】
在 SPFA 的基礎(chǔ)上,可以進一步的進行優(yōu)化,即 SLF 優(yōu)化
SLF 優(yōu)化就是 small label first 優(yōu)化,由于先擴展最小的點可以盡量使程序盡早結(jié)束,因此當(dāng)加入一個新點 u 的時候,如果此時的 dis[u] 比隊首的 dis[q.front()] 小的話,就將點 v 加入隊首,否則加入隊尾,利用 STL 中的雙端隊列 deque 即可??
struct Edge{int to,dis; }; vector<Edge> edge[N]; bool vis[N]; int dis[N]; void SPFA(int s) {memset(dis, INF, sizeof(dis));memset(vis, false, sizeof(vis));vis[s] = true;dis[s] = 0;deque<int> Q;Q.push_back(s);while (!Q.empty()) {int x = Q.front();Q.pop_front();vis[x] = 0;for (int i = 0; i < edge[x].size(); i++) {int y = edge[x][i].to;if (dis[y] > dis[x] + edge[x][i].to) {dis[y] = dis[x] + edge[x][i].to;if (!vis[y]) {vis[y] = true;if (!Q.empty() && dis[y] < dis[Q.front()])//加入隊首Q.push_front(y);else//加入隊尾Q.push_back(y);}}}} }【模版】
1.標(biāo)準(zhǔn)版
struct Edge {int to, next;int dis; } edge[N]; int head[N], tot; bool vis[N]; int dis[N]; void addEdge(int x, int y, int dis) {edge[++tot].to = y;edge[tot].dis = dis;edge[tot].next = head[x];head[x] = tot; } void SPFA(int S) {memset(vis, false, sizeof(vis));memset(dis, INF, sizeof(dis));dis[S] = 0;queue<int> Q;Q.push(S);while (!Q.empty()) {int x = Q.front();Q.pop();vis[x] = false;for (int i = head[x]; i != -1; i = edge[i].next) {int to = edge[i].to;if (dis[to] > dis[x] + edge[i].dis) {dis[to] = dis[x] + edge[i].dis;if (!vis[to]) {vis[to] = true;Q.push(to);}}}} } int main(){int n,m;while(scanf("%d%d",&n,&m)!=EOF){memset(head,-1,sizeof(head));for(int i=1;i<=m;i++){int x,y,dis;scanf("%d%d%d",&x,&y,&dis);//無向圖添邊兩次addEdge(x,y,dis);addEdge(y,x,dis);}int S;scanf("%d",&S);SPFA(S);for(int i=1;i<=n;i++)printf("%d\n",dis[i]);} return 0; }2.帶負環(huán)判斷的SPFA
struct Edge {int from, to;int dis;Edge() {}Edge(int from, int to, int dis) : from(from), to(to), dis(dis) {} }; struct SPFA {int n, m;Edge edges[N]; //所有的邊信息int head[N]; //每個節(jié)點鄰接表的頭int next[N]; //每個點的下一條邊int pre[N]; //最短路中的上一條弧bool vis[N];int dis[N];int cnt[N]; //進隊次數(shù)void init(int n) {this->n = n;this->m = 0;memset(head, -1, sizeof(head));}void AddEdge(int from, int to, int dist) {edges[m] = Edge(from, to, dist);next[m] = head[from];head[from] = m++;}bool negativeCycle(int s) { //判負環(huán)memset(vis, false, sizeof(vis));memset(cnt, 0, sizeof(cnt));memset(dis, INF, sizeof(dis));dis[s] = 0;queue<int> Q;Q.push(s);while (!Q.empty()) {int x = Q.front();Q.pop();vis[x] = false;for (int i = head[x]; i != -1; i = next[i]) {Edge &e = edges[i];if (dis[e.to] > dis[x] + e.dis) {dis[e.to] = dis[x] + e.dis;pre[e.to] = i;if (!vis[e.to]) {vis[e.to] = true;Q.push(e.to);if (++cnt[e.to] > n)return true;}}}}return false;} } spfa; int main() {int n, m;while (scanf("%d%d", &n, &m) != EOF) {spfa.init(n);int S;scanf("%d", &S);for (int i = 1; i <= m; i++) {int x, y, dis;scanf("%d%d%d", &x, &y, &dis);//無向邊添邊兩次spfa.AddEdge(x, y, dis);spfa.AddEdge(y, x, dis);}spfa.negativeCycle(S);for (int i = 1; i <= n; i++)printf("%d\n", spfa.dis[i]);}return 0; }?
總結(jié)
以上是生活随笔為你收集整理的图论 —— 最短路 —— Bellman-Ford 算法与 SPFA的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 训练日志 2019.7.27
- 下一篇: 信息学奥赛一本通(1043:整数大小比较