Bellman-Ford算法

Bellman-Ford算法的优点是可以发现负圈,缺点是时间复杂度比Dijkstra算法高。而SPFA算法是使用队列优化的Bellman-Ford版本,其在时间复杂度和编程难度上都比其他算法有优势。

Bellman-Ford算法流程分为三个阶段:

  • 第一步:数组Distant[i]记录从源点s到顶点i的路径长度,初始化数组Distant[n]为INF, Distant[s]为0;

  • 第二步:以下操作循环执行至多n-1次,n为顶点数: 对于每一条边e(u, v),如果Distant[u] + w(u, v) < Distant[v],则另Distant[v] = Distant[u]+w(u, v)。w(u, v)为边e(u,v)的权值; 若上述操作没有对Distant进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;

  • 第三步:为了检测图中是否存在负环路,即权值之和小于0的环路。对于每一条边e(u, v),如果存在Distant[u] + w(u, v) < Distant[v]的边,则图中存在负环路,即是说改图无法求出单源最短路径。否则数组Distant[n]中记录的就是源点s到各顶点的最短路径长度。

v的最短距离保存在 d[v]中。

在这个算法中,需要了解的几点

1、只有上一次迭代中松驰过的点才有可能参与下一次迭代的松弛操作。

似乎算法在遍历每一条边的做法会浪费时间,或许我们只需要考虑那些被成功松弛的点的邻点就可以了。我们可以用一个队列来维护这些被成功松弛的点,这个小小的改进可以节省很多时间,改进后的算法叫做SPFA

2、迭代的实际意义:每一次迭代k中,我们找到了经历了k条边的最短路

3、没有点能够松弛时,迭代结束

Bellman-Ford(G,w,s) :boolean   //图G ,边集 函数 w ,s为源点for each vertex v ∈ V(G) do        //初始化 1阶段d[v] ←+∞d[s] ←0;                             //1阶段结束for i=1 to |v|-1 do               //2阶段开始,双重循环。for each edge(u,v) ∈E(G) do //边集数组要用到,穷举每条边。If d[v]> d[u]+ w(u,v) then      //松弛判断d[v]=d[u]+w(u,v)               //松弛操作   2阶段结束for each edge(u,v) ∈E(G) doIf d[v]> d[u]+ w(u,v) thenExit falseExit true

SPFA

算法特点:在 Bellman-ford 算法的基础上加上一个队列优化,减少了冗余的松弛操作,是一种高效的最短路算法。

关键词:初始化,relax,队列

实现的过程和Bellman_Ford类似

【初始化】

dis数组全部赋值为INF,pre数组全部赋值为-1(表示还不知道前驱),

dis[s] = 0 表示源点不要求最短路径(或者最短路径就是0)。

【队列+松弛操作】

读取队头顶点u,并将队头顶点u出队(记得消除标记);将与点u相连的所有点v进行松弛操作,如果能更新估计值(即令d[v]变小),那么就更新,另外,如果点v没有在队列中,那么要将点v入队(记得标记),如果已经在队列中了,那么就不用入队,这样不断从队列中取出顶点来进行松弛操作。

以此循环,直到队空为止就完成了单源最短路的求解。

【算法过程】

设立一个队列用来保存待优化的顶点,优化时每次取出队首顶点 u,并且用 u 点当前的最短路径估计值dis[u]对与 u 点邻接的顶点 v 进行松弛操作,如果 v 点的最短路径估计值dis[v]可以更小,且 v 点不在当前的队列中,就将 v 点放入队尾。这样不断从队列中取出顶点来进行松弛操作,直至队列空为止。

【检测负权回路】

方法:如果某个点进入队列的次数大于等于 n,则存在负权回路,其中 n 为图的顶点数。

#include <iostream>
#include <queue>
#include <stack>
#include <cstring>
#include <cstdio>
#include <functional>
using namespace std;const int maxn = 99999999;
int map[205][205];//邻接矩阵
int d[205];//源点到顶点i的最短距离
bool vis[205];//标记数组
int enqueue_num[205];//记录入队次数
int path[205];//记录最短的路径
int n;//顶点数
int e;//边数
int start;//源点bool SPFA()
{memset(vis,false,sizeof(vis));memset(enqueue_num,0,sizeof(enqueue_num));for(int i=0;i<n;i++){d[i] = maxn;path[i] = start;}queue<int> Q;Q.push(start);d[start] = 0;vis[start] = 1;enqueue_num[start]++;int v;while(Q.empty()!=1){int u = Q.front();Q.pop();vis[u] = 0;for(v=0;v<n;v++){if(map[u][v]!=maxn)//u 与 v 直接邻接{if(d[u]+map[u][v]<d[v]){d[v] = d[u] + map[u][v];path[v] = u;if(!vis[v]){Q.push(v);enqueue_num[v]++;if(enqueue_num[v]>=n){return false;}vis[v] = true;}}}}}return true;
}void print()
{int i;for(i=0;i<n;i++){if(i!=start){int p = i;stack<int> s;cout << "顶点 " << start << " 到顶点 " << p << "最短路是:";while(start!=p){s.push(p);p=path[p];}cout << start;while(s.empty()!=1){cout << "---" << s.top();s.pop();}cout << "    最短路径的长度是:" << d[i] << endl; }}
}int main()
{int i, j;cout << "请输入图的顶点数,边数,源点:\n";cin >> n >> e >> start;//初始化for(i=0;i<=n;i++){for(j=0;j<=n;j++){if(i==j)   map[i][j] = 0;else map[i][j] = maxn;}}cout << "请输入" << e << "条边的信息:\n";int u, v, w;//默认是有向图for(i=0;i<e;i++){cin >> u >> v >> w;map[u][v] = w;}if(SPFA())print();elsecout << "Sorry,it has negative circle!\n";return 0;
}

用下面这张图进行测试数据:

现学现用

http://poj.org/problem?id=3259

【问题描述】

虫洞是很奇特的,因为它是一个**单向**通道,可让你进入虫洞的前达到目的地!他的N(1≤N≤500)个农场被编号为1..N,之间有M(1≤M≤2500)条路径,W(1≤W≤200)个虫洞。FJ作为一个狂热的时间旅行的爱好者,他要做到以下几点:开始在一个区域,通过一些路径和虫洞旅行,他要回到最开时出发的那个区域出发前的时间。也许他就能遇到自己了:)。为了帮助FJ找出这是否是可以或不可以,他会为你提供F个农场的完整的图(1≤F≤5)。所有的路径所花时间都不大于10000秒,所有的虫洞都回到不大于10000秒之前。

【Input】

第1行:一个整数F表示接下来会有F个农场说明。 每个农场第一行:分别是三个空格隔开的整数:N,M和W 第2行到M+1行:三个空格分开的数字(S,E,T)描述,分别为:需要T秒走过S和E之间的双向路径。两个区域可能由一个以上的路径来连接。 第M +2到M+ W+1行:三个空格分开的数字(S,E,T)描述虫洞,描述单向路径,S到E且回溯T秒。

【Output】

F行,每行代表一个农场 每个农场单独的一行,” YES”表示能满足要求,”NO”表示不能满足要求

【样例输入】

2
3 3 1
1 2 2
1 3 4
2 3 1
3 1 3
3 2 1
1 2 3
2 3 4
3 1 8

【样例输出】

NO
YES

题意是问是否能通过虫洞回到过去;虫洞是一条单向路,不但会把你传送到目的地,而且时间会倒退T秒。我们把虫洞看成是一条负权路,问题就转化成求一个图中是否存在负权回路;

Bellman_ford 算法解题

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;const int maxn = 9999999;typedef struct Edge
{int u,v;int weight;
}Edge;
Edge edge[5200];
int d[1010];
int N,M,W,F,all_e;bool bellman_ford()
{int i,j;bool flag;for(i=1;i<=N-1;i++){flag = false;for(j=0;j<all_e;j++){if(d[edge[j].v]>d[edge[j].u]+edge[j].weight){d[edge[j].v]=d[edge[j].u]+edge[j].weight;flag = true;}}if(!flag) return false;//不存在负环}for(i=0;i<all_e;i++){if(d[edge[i].v]>d[edge[i].u]+edge[i].weight)return true;//存在}return false;
}int main()
{int i,j;cin >> F;while(F--){cin >> N >> M >> W;memset(d,maxn,sizeof(d));all_e = 0;int u,v,w;//注意是无向图,双向的for(i=1;i<=M;i++){cin >> u >> v >> w;edge[all_e].u = u;edge[all_e].v = v;edge[all_e++].weight = w;edge[all_e].u = v;edge[all_e].v = u;edge[all_e++].weight = w;}//题目中明确指出,虫洞是单向的for(i=1;i<=W;i++){cin >> u >> v >> w;edge[all_e].u = u;edge[all_e].v = v;edge[all_e++].weight = -w;}if(bellman_ford())cout << "YES\n";elsecout << "NO\n";}return 0;
}

Floyd算法

也可以求解,但是,很容易就超时,AC的话时间也会比Bellman_ford算法多很多

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;const int maxn = 99999999;
int map[520][520];
int F,N,M,W;bool bellman_ford()
{int i,j,k;for(k=1;k<=N;k++){for(i=1;i<=N;i++){for(j=1;j<=N;j++){if(map[i][j]>map[i][k]+map[k][j]){map[i][j]=map[i][k]+map[k][j];}}if(map[i][i]<0){return true;}}}return false;
}int main()
{int i,j;cin >> F;while(F--){cin >> N >> M >> W;for(i=0;i<=N;i++){for(j=0;j<=N;j++){if(i==j)    map[i][j] = 0;else map[i][j] = maxn;}}int a,b,w;//需要注意的是:正权的边是单向的,负权的边是双向的。for(i=1;i<=M;i++){cin >> a >> b >> w;if(map[a][b]>w){map[a][b] = w;map[b][a] = w;}}for(i=1;i<=W;i++){cin >> a >> b >> w;map[a][b] = -w;}if(bellman_ford())cout << "YES\n";elsecout << "NO\n";}return 0;
}

SPFA(Bellman_ford + 队列 优化)

#include <iostream>
#include <queue>
#include <cstring>
#include <cstdio>
#include <stack>
using namespace std;const int maxn = 9999999;
int map[520][520];
int d[520];
bool vis[520];
int enqueue_num[520];
int M,N,W;
int F;bool SPFA()
{memset(vis,false,sizeof(vis));memset(enqueue_num,0,sizeof(enqueue_num));memset(d,maxn,sizeof(d));queue<int> Q;Q.push(1);d[1] = 0;vis[1] = true;enqueue_num[1]++;int v;while(Q.empty()!=1){int u = Q.front();Q.pop();vis[u] = false;for(v=1;v<=N;v++){if(map[u][v]!=maxn){if(d[u]+map[u][v]<d[v]){d[v]=d[u]+map[u][v];if(!vis[v]){Q.push(v);enqueue_num[v]++;if(enqueue_num[v]>=N){return false;//有负圈}vis[v] = true;}}}}}return true;//没有负圈
}int main ()
{int i,j;cin >> F;while(F--){cin >> N >> M >> W;for(i=0;i<=N;i++){for(j=0;j<=N;j++){if(i==j)    map[i][j] = 0;else map[i][j] = maxn;}}int a,b,c;for(i=1;i<=M;i++){cin >> a >> b >> c;if(map[a][b]>c){map[a][b] = c;map[b][a] = c;}}for(i=1;i<=W;i++){cin >> a >> b >> c;map[a][b] = -c;}if(SPFA())cout << "NO\n";elsecout << "YES\n";}return 0;
}

推荐博客:

最短路径算法—Bellman-Ford(贝尔曼-福特)算法分析与实现(C/C++)

【POJ/算法】 3259 Wormholes(Bellman-Ford算法, SPFA ,FLoyd算法)相关推荐

  1. C++实现bellman ford贝尔曼-福特算法(最短路径)(附完整源码)

    C++实现bellman ford贝尔曼-福特算法 实现bellman ford贝尔曼-福特算法的完整源码(定义,实现,main函数测试) 实现bellman ford贝尔曼-福特算法的完整源码(定义 ...

  2. POJ 3259 Wormholes【最短路/SPFA判断负环模板】

    农夫约翰在探索他的许多农场,发现了一些惊人的虫洞.虫洞是很奇特的,因为它是一个单向通道,可让你进入虫洞的前达到目的地!他的N(1≤N≤500)个农场被编号为1..N,之间有M(1≤M≤2500)条路径 ...

  3. 【Java数据结构与算法】第二十章 Dijkstra算法和Floyd算法

    第二十章 Dijkstra算法和Floyd算法 文章目录 第二十章 Dijkstra算法和Floyd算法 一.Dijkstra算法 1.介绍 2.代码实现 二.Floyd算法 1.介绍 2.代码实现 ...

  4. 基础算法题——城市间最短路程(Floyd算法)

    Floyd算法 通过一个图的权值矩阵求出它的每两点间的最短路径矩阵. 其状态转移方程如下:map[i,j]:=min{map[i,k]+map[k,j],map[i,j]} 问题描述 现已知有N(N& ...

  5. 最短路径:Dijkstra算法(求单源最短路径)Floyd算法(求各顶点之间最短路径)

    最短路径: 在一个带权图中,顶点V0到图中任意一个顶点Vi的一条路径所经过边上的权值之和,定义为该路径的带权路径长度,把带权路径最短的那条路径称为最短路径. DiskStra算法: 求单源最短路径,即 ...

  6. 【POJ】3259 Wormholes

    题目链接:http://poj.org/problem?id=3259 题意:n个农场,m条双向路径,w条单向路径(虫洞).单向虫洞路径是负值.农夫想知道自己能不能看到自己(X). 题解:其实刚开始没 ...

  7. 图解Bellman Ford算法

    Bellman Ford 单源最短路径算法[中字] Bellman Ford 单源最短路径算法[中字]_哔哩哔哩_bilibili 贝尔曼-福特算法(Bellman–Ford algorithm )油 ...

  8. 最短路径(Floyd算法)(c/c++)

    如果要得到图中各个顶点之间的最短路径,方法1:可以对每一个顶点采用Dijkstra算法:方法2:可以采用Floyd算法,它是一种用来求双源点之间最短路径的算法,采用邻接矩阵来存储图 辅助结构 int ...

  9. Floyd算法的动态规划本质

    Floyd–Warshall(简称Floyd算法)是一种著名的解决任意两点间的最短路径(All Paris Shortest Paths,APSP)的算法.从表面上粗看,Floyd算法是一个非常简单的 ...

  10. dijkstra算法和floyd算法(C语言)

    dijkstra算法: /* 邻接表存储 - 无权图的单源最短路算法 *//* dist[]和path[]全部初始化为-1 */ void Unweighted ( LGraph Graph, int ...

最新文章

  1. RecyclerView上拉加载Demo
  2. 深入分析python的and or 返回值
  3. spark之2:原理介绍
  4. ActiveMQ –经纪人网络解释
  5. QS首发大学百强排名,华东五校表现惊艳,老牌985望尘莫及
  6. 大数据— Hadoop
  7. Python中if判断语句在只有一个break子句时可以写在一行
  8. 蓝桥杯 ADV-136算法提高 大数加法
  9. UnauthorizedAccessException Invaild cross-thread access
  10. Audiority Big Goat Mac(模拟建模复古失真插件)
  11. 委托与Lambda表达式
  12. 团队-象棋游戏-开发环境搭建过程
  13. 使用ceph-deploy 部署集群
  14. McAfee官方卸载工具下载及使用
  15. centos8安装docker使用smartdns+adguardhome,完美高效加速dns和去广告
  16. React修改图片大小
  17. android这只椭圆背景,Android 背景样式shape - oval椭圆、圆
  18. kml用什么软件打开?
  19. jpi多表联查_使用mybatis进行多表联查
  20. 美团点评_给出两个字符串(可能包含空格),找出其中最长的公共连续子串,输出其长度。

热门文章

  1. hung-yi lee_p15_机器学习深层网络比浅层网络好在哪
  2. ZJU-java进阶笔记 第三周(对象容器)
  3. MySQL编码引发的两个问题
  4. 深入浅出JVM的锁优化案例
  5. 阿里发布AliGenie2.0系统,“百箱大战”用上视觉武器
  6. 免费的私人代码托管(bitbucket) 和 常用git指令
  7. corosync+pacemaker+crm简单配置
  8. QCon2016旧金山大会焦点分享者确认
  9. 开源 免费 java CMS - FreeCMS1.9 全文检索
  10. php减少损耗的方法之一 缓存对象