天天看点

Bellman-Ford算法Bellman Ford算法

Bellman Ford算法

用途

用于解决单源最短路径问题,即在给定图和起始结点的基础上,求所有其他结点到起始结点的最短路径。与Dijkstra算法不同的是,BF算法可以处理含有负边权边的图(如果图中存在负环则不适用,因为路径可以不断穿过负环来使最短路径越来越小,无法解出答案)。

算法描述

与Dijkstra算法相似,BF算法也是利用已经找到的最短路径来更新其他结点到源点的最短路径。不同的是,Dij是有针对性的,每次并入一个距离最短的新结点后,再以该点为中转站来更新其他未访问结点到源点的最短距离;而BF算法每一次都要遍历所有 结点u -> 结点v 的边,如果以u为中转站能使v距离源点更近,则更新v到源点的最短距离(该操作成为松弛),如果不存在负环则一共需要遍历 n - 1 次(最坏情况是所有结点连成一直链才能构成源点出发的最短路径,除源点外共有 n - 1 层,每次松弛操作至少会使一层达到最优,所以至多遍历 n - 1 次),其主要步骤如下:

  1. 设置数组d,用以记录从源点s到各个结点的最短距离。初始化时,d[s] = 0, 其余所有结点 d[i] = INF (INF表示无穷大);
  2. 循环 n - 1 次,每次遍历所用边,更新每个结点到源点的最短距离(松弛),如果不存在负环,则此时数组d中各值应当已经全部优化完成(这步中可以增加剪枝操作:判断此次遍历中是否有d[i]需要更新,如果没有,说明所有最短距离已经达到最优,可以直接结束算法);
  3. 再次遍历所有边,如果仍有结点的最短距离可以更新,说明图中存在负环,返回false,表明无解;
  4. 返回true,结束算法。

代码实现——邻接表

const int maxn = 1000;
const int INF = 1000000000;

struct node
{
    int v;      // 结点编号
    int l;      // 边权
};

vector<node> adjL[maxn];
int n;
int d[maxn];        // 记录各结点到源点的最短距离

bool BellmanFord(int s)
{
    for (int i = 0; i < n; i++)     // 初始化最短距离
        d[i] = INF;
    d[s] = 0;       // 到自身距离为0
    
    for (int i = 0; i < n - 1; i++)
    {
        bool flag = true;       // 判断此次循环是否进行了松弛操作
        for (int u = 0; u < n; u++)
        {
            for (int j = 0; j < adjL[u].size(); j++)
            {
                int v = adjL[u][j].v;
                int l = adjL[u][j].l;
                if (d[u] + l < d[v])    // 以u为中转站能使v离源点更近
                {
                    d[v] = d[u] + l;
                    flag = false;       // 若进行松弛,更新标记
                }
            }
        }
        if (flag)               // 剪枝,若无松弛操作说明d中所有值已达到最优
            return true;
    }
    
    for (int u = 0; u < n; u++)     // 第n次遍历用来判断是否存在负环
    {
        for (int j = 0; j < adjL[u].size(); j++)
        {
            int v = adjL[u][j].v;
            int l = adjL[u][j].l;
            if (d[u] + l < d[v])    // 最短距离还能更新,说明存在负环
                return false;       // 负环无解
        }
    }
    
    return true;
}
           

继续阅读