单源点最短路径算法:Bellman-Ford算法

背景知识

图简介

图由节点和边组成,边有方向的图称为有向图,边没有方向的图称为无向图,最短路径算法里可以把无向图视为双向连接的有向图。
边有权重的图称为有权图,边没有权重的图称为无权图,无权图可以视为边的权重均为1的图。

单源点最短路径

给定图中的一个节点,求该节点到其他所有节点的最短路径。

Bellman-Ford算法

概述

Bellman-Ford属于DP算法,适用于含负权边的图,并且可以检测图中是否含有负权环(意味着没有最短路径),时间复杂度为O(VE)

核心思想

对所有边进行V-1次遍历,第一次遍历获得边数为1的最短路径,第二次遍历获得边数为2的最短路径,以此类推,第V-1次遍历获得边数为V-1的最短路径。V-1次遍历后,进行第V次遍历,如果能够获得更小的路径长度,说明存在负环。

算法步骤

dis[]用于存储每个节点到源点src的距离,preNode[]存储每个节点的前驱节点
Step1:初始化dis[]为INT_MAX,dis[src]初始化为0. preNode[src]初始化为-1.
Step2:遍历图中的每条边edge(v,u),若dis[v]不为INT_MAX,且dis[v] + weight(v,u) < dis[u],执行dis[u] = dis[v] + weight(v,u)。
Step3:重复Step2 V次,若某次遍历没有执行dis[u] = dis[v] + weight(v,u),即发生了松弛,结束Step3
Step4:判断Step3最后一次执行是否发生松弛,若发生,说明图中含有负权环,若没有发生,说明图中没有负权环。

为什么能获得最短路径

可以从直观上理解,第一次遍历获得了(距离源点)边数量为1的最短路径,第二次在第一次的基础上获得了边数量为2的最短路径,第三次在第二次的基础上获得了边数量为3的最短路径,依次类推,有些节点到源点有很多条路径,这些路径的边数可能不一样也可能一样,算法会在V-1循环中比较这些路径的长度,取最小值作为最短路径(遍历中边数多的可能距离可能反而小于边数少的,这个时候就会发生松弛了)。

为什么最后一次遍历是否发生松弛可以判断负环

有两种情况会导致Step3结束,一是当次遍历没有发生松弛,二是已经循环了V次,情况一显然不存在负环,不然肯定可以继续松弛。情况二下,如果最后一次遍历发生了松弛,说明第V次松弛了,意味着找到的边长数为V的最短路径,而最长的最短路劲应该是V-1条边, 所以肯定至少一条边重复了,一定是出现环了,环路径比非环更短,说明是负环。
举个最简单的例子:
《单源点最短路径算法:Bellman-Ford算法》
第一次遍历 dis[1] = 2, dis[2] =INT_MAX
第二次遍历 dis[1] = 2, dis[2] = 3;
已经遍历了V-1即两次了,第3次遍历,dis[1] = dis[2] + wegiht(2,1) = 1,小于dis[1],所以会继续发生松弛。
如果weight(1,2) = 3,环长度为3-2=1,正环,这个时候第V次遍历就不会发生松弛了。

C++实现

#include <iostream>
#include <vector>

using namespace std;

class Solution
{
public:
    vector<int> bellmanFord(int src, vector<vector<int>> &graph, vector<int> &preNode)
    {
        preNode = vector<int>(graph.size(), -1);
        ///transform to vector<pair<int,int>>
        vector<pair<int, int>> arcs;
        for(int i = 0; i < graph.size(); i++)
        {
            for(int j = 0; j < graph.size(); j++)
            {
                if(graph[i][j] != 0)
                {
                    arcs.push_back(pair<int, int>(i, j));
                }
            }
        }

        ///intialize distance array
        vector<int> distToSrc(graph.size(), INT_MAX);
        distToSrc[src] = 0;

        bool isSlacked = false;
        ///traverse and slack
        for(int i = 0; i < graph.size(); i++)
        {
            isSlacked = false;
            for(auto arc: arcs)
            {
                if(distToSrc[arc.first] != INT_MAX &&
                   distToSrc[arc.first] + graph[arc.first][arc.second] < distToSrc[arc.second])
                {
                    distToSrc[arc.second] = distToSrc[arc.first] + graph[arc.first][arc.second];
                    preNode[arc.second] = arc.first;
                    isSlacked = true;
                }
            }
            ///no slack in on traverse, break;
            if(!isSlacked)
            {
                break;
            }
        }
        /* *ifSlacked == true, means V's traverse is slacked which means *have negative circle. otherwise isSlacked should be false */
        if(isSlacked)
        {
            return vector<int>();
        }
        return distToSrc;
    }

    int printPath()
    {

        vector<vector<int>> graph{{0, 4, 0, 0, 0, 0, 0, 8, 0},
                                  {4, 0, 8, 0, 0, 0, 0, 11, 0},
                                  {0, 8, 0, 7, 0, 4, 0, 0, 2},
                                  {0, 0, 7, 0, 9, 14, 0, 0, 0},
                                  {0, 0, 0, 9, 0, 10, 0, 0, 0},
                                  {0, 0, 4, 14, 10, 0, 2, 0, 0},
                                  {0, 0, 0, 0, 0, 2, 0, 1, 6},
                                  {8, 11, 0, 0, 0, 0, 1, 0, 7},
                                  {0, 0, 2, 0, 0, 0, 6, 7, 0}};

        vector<int> preNode;
        auto dists = bellmanFord(0, graph, preNode);

        ///print
        for(int i = 0; i < dists.size(); i++)
        {
            cout << i << " " << dists[i] << ": ";
            int node = i;
            stack<int> s;
            while(node != -1)
            {
                s.push(node);
                node = preNode[node];
            }
            while(!s.empty())
            {
                cout << s.top() << " ";
                s.pop();
            }
            cout << endl;
        }
    }

int main()
{
    Solution().printPath();
    return 0;
}

The End

    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/damontive/article/details/79114073
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞