当前位置:   article > 正文

图论最短路径——Bellman-Ford Algorithm算法_贝尔曼福特算法

贝尔曼福特算法

贝尔曼-福特算法:处理负权边的最短路径算法

图论中,寻找最短路径是一个常见且重要的问题。对于这个问题,有许多算法可以解决,其中最著名的是 Dijkstra 算法。然而,当图中包含负权边时,Dijkstra 算法可能无法正确工作。这时,贝尔曼-福特(Bellman-Ford)算法就派上了用场。

贝尔曼-福特算法的基本原理

贝尔曼-福特算法可以在含有负权边的图中找到从单个源点到所有其他顶点的最短路径。这个算法的核心是反复“松弛”图中的所有边。所谓“松弛”,指的是更新当前找到的到各顶点的最短路径估计。

算法步骤

  1. 初始化:将所有顶点的最短路径估计值初始化为无穷大,只有源点的最短路径估计值设置为0。

  2. 边的松弛:对每条边进行松弛操作。如果当前顶点v到顶点u的最短路径估计加上从v到u的边的权重比当前顶点u的最短路径估计更小,那么就更新顶点u的最短路径估计。

  3. 重复执行:上述步骤在所有边上重复进行 |V|-1 次,其中 |V| 是顶点的数量。

  4. 检测负权环:最后,再次对所有边进行松弛操作,如果还能找到更短的路径,则说明图中存在负权环。

时间复杂度

贝尔曼-福特算法的时间复杂度为 O(VE),其中 V 是顶点数,E 是边数。这比 Dijkstra 算法的时间复杂度高,但它能处理更复杂的情况。

负权环检测

贝尔曼-福特算法的一个重要特性是能够检测图中是否存在负权回路。如果在完成标准的 |V|-1 次迭代后,再进行一次所有边的遍历,如果这时还能找到更短的路径,则说明图中存在负权回路。

代码实现

边的定义

  1. struct Edge {
  2. int to;
  3. int weight;
  4. Edge(int t, int w) :to(t), weight(w) {}
  5. };

用邻接表初始化一个图

  1. int n = 5;//顶点数
  2. vector<vector<Edge>>graph(n);
  3. graph[0].push_back(Edge(1, 10));
  4. graph[0].push_back(Edge(2, 5));
  5. graph[1].push_back(Edge(2, 2));
  6. graph[1].push_back(Edge(3, 1));
  7. graph[2].push_back(Edge(1, 3));
  8. graph[2].push_back(Edge(3, 9));
  9. graph[2].push_back(Edge(4, 2));
  10. graph[3].push_back(Edge(4, 4));
  11. graph[4].push_back(Edge(3, 6));
  12. graph[4].push_back(Edge(0, 7));

bellman算法实现

  1. //graph是用邻接表表示的图
  2. vector<int> bellman(const vector<vector<Edge>>& graph, const int& src) {
  3. int n = graph.size();
  4. //dis储存从出发节点到目标节点的最短距离,初始化为极大值
  5. vector<int> dis(n, INT_MAX);
  6. //出发节点到自身的距离为0
  7. dis[src] = 0;
  8. // 进行 n-1 次循环,如果某次循环时dis数组没有被更新则结束循环
  9. for (int k = 0; k < n - 1; k++) {
  10. //对每一个节点进行遍历
  11. for (int i = 0; i < n; i++) {
  12. //遍历每个节点的每条边
  13. for (const Edge& edge : graph[i]) {
  14. int u = i;
  15. int v = edge.to;
  16. int w = edge.weight;
  17. // 如果找到更短的路径则更新
  18. if (dis[u] < INT_MAX && dis[u] + w < dis[v]) {
  19. dis[v] = dis[u] + w;
  20. }
  21. }
  22. }
  23. }
  24. // 检测负权回路,更新dis数组时对图遍历了n-1次,如果第n次遍历仍然能找到更短的路径则说明图中存在负权回路
  25. for (int i = 0; i < n; i++) {
  26. for (const Edge& edge : graph[i]) {
  27. int u = i;
  28. int v = edge.to;
  29. int w = edge.weight;
  30. // 如果仍能找到更短的路径,说明存在负权回路
  31. if (dis[u] < INT_MAX && dis[u] + w < dis[v]) {
  32. cout << "图中存在负权回路" << endl;
  33. return vector<int>(); // 或者可以返回一个特殊值表示存在负权回路
  34. }
  35. }
  36. }
  37. return dis;
  38. }

与Dijkstra算法的比较

  1. #include <iostream>
  2. #include <vector>
  3. #include<queue>
  4. using namespace std;
  5. struct Edge {
  6. int to;
  7. int weight;
  8. Edge(int t, int w) :to(t), weight(w) {}
  9. };
  10. //graph是用邻接表表示的图
  11. vector<int>dijkstra(const vector<vector<Edge>>& graph, int src) {
  12. int n = graph.size();
  13. //容器定义
  14. //储存各个顶点到src顶点的距离
  15. vector<int>dis(n, INT_MAX);
  16. //记录访问过的顶点
  17. vector<bool>vis(n, false);
  18. //用优先级队列来处理距离最短的顶点,pair<int,int>的第一个int存储距离,第二个int存储顶点;底层用vector来存储这个队列;greater表示从小到大排
  19. priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>>pq;
  20. //src顶点到自己的距离为0
  21. dis[src] = 0;
  22. pq.push({ 0,src });
  23. while (!pq.empty()) {
  24. //v表示当前距离最短的顶点
  25. int v = pq.top().second; pq.pop();
  26. //若是访问过当前顶点则跳过
  27. if (vis[v])continue;
  28. vis[v] = true;
  29. //访问邻接顶点
  30. for (const auto& edge : graph[v]) {
  31. int t = edge.to;
  32. int w = edge.weight;
  33. if (!vis[t] && w + dis[v] < dis[t]) {
  34. dis[t] = w + dis[v];
  35. pq.push({ dis[t],t });
  36. }
  37. }
  38. }
  39. return dis;
  40. }
  41. //graph是用邻接表表示的图
  42. vector<int> bellman(const vector<vector<Edge>>& graph, const int& src) {
  43. int n = graph.size();
  44. //dis储存从出发节点到目标节点的最短距离,初始化为极大值
  45. vector<int> dis(n, INT_MAX);
  46. //出发节点到自身的距离为0
  47. dis[src] = 0;
  48. // 进行 n-1 次循环,如果某次循环时dis数组没有被更新则结束循环
  49. for (int k = 0; k < n - 1; k++) {
  50. //对每一个节点进行遍历
  51. for (int i = 0; i < n; i++) {
  52. //遍历每个节点的每条边
  53. for (const Edge& edge : graph[i]) {
  54. int u = i;
  55. int v = edge.to;
  56. int w = edge.weight;
  57. // 如果找到更短的路径则更新
  58. if (dis[u] < INT_MAX && dis[u] + w < dis[v]) {
  59. dis[v] = dis[u] + w;
  60. }
  61. }
  62. }
  63. }
  64. // 检测负权回路,更新dis数组时对图遍历了n-1次,如果第n次遍历仍然能找到更短的路径则说明图中存在负权回路
  65. for (int i = 0; i < n; i++) {
  66. for (const Edge& edge : graph[i]) {
  67. int u = i;
  68. int v = edge.to;
  69. int w = edge.weight;
  70. // 如果仍能找到更短的路径,说明存在负权回路
  71. if (dis[u] < INT_MAX && dis[u] + w < dis[v]) {
  72. cout << "图中存在负权回路" << endl;
  73. return vector<int>(); // 或者可以返回一个特殊值表示存在负权回路
  74. }
  75. }
  76. }
  77. return dis;
  78. }
  79. int main() {
  80. int n = 5;//顶点数
  81. vector<vector<Edge>>graph(n);
  82. graph[0].push_back(Edge(1, 10));
  83. graph[0].push_back(Edge(2, 5));
  84. graph[1].push_back(Edge(2, 2));
  85. graph[1].push_back(Edge(3, 1));
  86. graph[2].push_back(Edge(1, 3));
  87. graph[2].push_back(Edge(3, 9));
  88. graph[2].push_back(Edge(4, 2));
  89. graph[3].push_back(Edge(4, 4));
  90. graph[4].push_back(Edge(3, 6));
  91. graph[4].push_back(Edge(0, 7));
  92. vector<int>shortest_path = dijkstra(graph, 0);
  93. vector<int>sp = bellman(graph, 0);
  94. cout << shortest_path[3] << endl;//输出9
  95. cout << sp[3] << endl;//输出9
  96. return 0;
  97. }

 这个图中不存在负权路径,不能体现bellman算法的优势

对于这种带负权的图dijkstra算法就失效了

适用场景

由于其能够处理负权边的特性,贝尔曼-福特算法非常适合于需要处理复杂权重系统的场景,如经济系统、交通网络等领域的建模。

结语

尽管贝尔曼-福特算法在时间复杂度上不如某些其他算法,但其能力处理更为复杂的图形结构,特别是含有负权边的图,使其成为一个非常有用的工具。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/710983
推荐阅读
相关标签
  

闽ICP备14008679号