了解贝尔曼·福特算法

    Bellman-Ford算法有助于我们找到加权图中从一个顶点到所有其他顶点的最短路径。
    它与Dijkstra算法相似,但可用于边具有负权重的图。

为什么在现实生活中会有负权重的边?

    负权重边一开始似乎没用,但它们可以解释许多现象,如现金流、化学反应中释放/吸收的热量等。
    例如,如果从一种化学物质A到达另一种化学物质B的方法不同,则每种方法都会具有散热和吸热的副反应。
    如果我们想找到一组需要最小能量的反应,那么我们需要将吸热作为负权重,将散热作为正权重。

为什么我们要留意负权重?

    负权重边可以创建负权重循环,即通过返回到同一点来减少总路径距离的循环。
在这里插入图片描述
    当试图找出最短路径时,负权重循环可能会给出错误的结果
    最短路径算法,如Dijkstra的算法,不能检测到这样的循环,可能会给出一个不正确的结果,因为他们可以使用负权重循环,并减少路径长度。

贝尔曼·福特算法如何工作

    Bellman-Ford算法的工作原理是高估从起始顶点到所有其他顶点的路径长度。然后,它通过找到更短的新路径来逐步调整先前的估计。
    通过对所有顶点重复执行此操作,我们可以保证结果得到优化。
    第一步,从权重图开始。
在这里插入图片描述
    第二步,选择一个顶点,将其他顶点的路径值赋值为无穷大。
在这里插入图片描述
    第三步,访问每个节点,如果路径距离不正确,则进行修正。
在这里插入图片描述
    第四步,我们要做5次,因为在最差情况下,一个顶点的路径长度需要被重新调整5次。
在这里插入图片描述
    第五步,注意右上方角落的定点是如何调整路径长度的。
在这里插入图片描述
    第六步,当所有顶点都有路径长度后,我们检查是否存在负循环。
在这里插入图片描述

贝尔曼·福特伪码

    我们需要维持每个顶点的路径距离。我们可以将其存储在一个大小为v的数组中,其中v是顶点的数目。
    我们还希望能够获得最短路径,不仅知道最短路径的长度。为此,我们将每个顶点映射到最后更新其路径长度的顶点。
    一旦算法结束,我们就可以从目标顶点回溯到源顶点来找到路径。

function bellmanFord(G, S)
  for each vertex V in G
    distance[V] <- infinite
      previous[V] <- NULL
  distance[S] <- 0

  for each vertex V in G				
    for each edge (U,V) in G
      tempDistance <- distance[U] + edge_weight(U, V)
      if tempDistance < distance[V]
        distance[V] <- tempDistance
        previous[V] <- U

  for each edge (U,V) in G
    If distance[U] + edge_weight(U, V) < distance[V}
      Error: Negative Cycle Exists

  return distance[], previous[]
Bellman Ford vs Dijkstra

    Bellman-Ford算法和Dijkstra算法在结构上非常相似。虽然Dijkstra只查看顶点的直接邻居,但Bellman在每次迭代中都会遍历每一条边。
在这里插入图片描述

C示例
// Bellman Ford Algorithm in C

#include <stdio.h>
#include <stdlib.h>

#define INFINITY 99999

//struct for the edges of the graph
struct Edge {
    
    
  int u;  //start vertex of the edge
  int v;  //end vertex of the edge
  int w;  //weight of the edge (u,v)
};

//Graph - it consists of edges
struct Graph {
    
    
  int V;        //total number of vertices in the graph
  int E;        //total number of edges in the graph
  struct Edge *edge;  //array of edges
};

void bellmanford(struct Graph *g, int source);
void display(int arr[], int size);

int main(void) {
    
    
  //create graph
  struct Graph *g = (struct Graph *)malloc(sizeof(struct Graph));
  g->V = 4;  //total vertices
  g->E = 5;  //total edges

  //array of edges for graph
  g->edge = (struct Edge *)malloc(g->E * sizeof(struct Edge));

  //------- adding the edges of the graph
  /*
		edge(u, v)
		where 	u = start vertex of the edge (u,v)
				v = end vertex of the edge (u,v)
		
		w is the weight of the edge (u,v)
	*/

  //edge 0 --> 1
  g->edge[0].u = 0;
  g->edge[0].v = 1;
  g->edge[0].w = 5;

  //edge 0 --> 2
  g->edge[1].u = 0;
  g->edge[1].v = 2;
  g->edge[1].w = 4;

  //edge 1 --> 3
  g->edge[2].u = 1;
  g->edge[2].v = 3;
  g->edge[2].w = 3;

  //edge 2 --> 1
  g->edge[3].u = 2;
  g->edge[3].v = 1;
  g->edge[3].w = 6;

  //edge 3 --> 2
  g->edge[4].u = 3;
  g->edge[4].v = 2;
  g->edge[4].w = 2;

  bellmanford(g, 0);  //0 is the source vertex

  return 0;
}

void bellmanford(struct Graph *g, int source) {
    
    
  //variables
  int i, j, u, v, w;

  //total vertex in the graph g
  int tV = g->V;

  //total edge in the graph g
  int tE = g->E;

  //distance array
  //size equal to the number of vertices of the graph g
  int d[tV];

  //predecessor array
  //size equal to the number of vertices of the graph g
  int p[tV];

  //step 1: fill the distance array and predecessor array
  for (i = 0; i < tV; i++) {
    
    
    d[i] = INFINITY;
    p[i] = 0;
  }

  //mark the source vertex
  d[source] = 0;

  //step 2: relax edges |V| - 1 times
  for (i = 1; i <= tV - 1; i++) {
    
    
    for (j = 0; j < tE; j++) {
    
    
      //get the edge data
      u = g->edge[j].u;
      v = g->edge[j].v;
      w = g->edge[j].w;

      if (d[u] != INFINITY && d[v] > d[u] + w) {
    
    
        d[v] = d[u] + w;
        p[v] = u;
      }
    }
  }

  //step 3: detect negative cycle
  //if value changes then we have a negative cycle in the graph
  //and we cannot find the shortest distances
  for (i = 0; i < tE; i++) {
    
    
    u = g->edge[i].u;
    v = g->edge[i].v;
    w = g->edge[i].w;
    if (d[u] != INFINITY && d[v] > d[u] + w) {
    
    
      printf("Negative weight cycle detected!\n");
      return;
    }
  }

  //No negative weight cycle found!
  //print the distance and predecessor array
  printf("Distance array: ");
  display(d, tV);
  printf("Predecessor array: ");
  display(p, tV);
}

void display(int arr[], int size) {
    
    
  int i;
  for (i = 0; i < size; i++) {
    
    
    printf("%d ", arr[i]);
  }
  printf("\n");
}
贝尔曼·福特算法的复杂度
时间复杂度
最佳案例复杂度 O(E)
平均情况复杂度 O(VE)
最坏情况的复杂度 O(VE)
空间复杂度

    空间复杂度为O(V)。

贝尔曼·福特算法应用
  1. 用于计算路由算法中的最短路径
  2. 寻找最短路径
参考文档

[1]Parewa Labs Pvt. Ltd.Bellman Ford’s Algorithm[EB/OL].https://www.programiz.com/dsa/bellman-ford-algorithm,2020-01-01.

猜你喜欢

转载自blog.csdn.net/zsx0728/article/details/114685608