• Bellman-Ford与SPFA


    一、Bellman-Ford

    Bellman-Ford 算法是一种用于计算带权有向图中单源最短路径(当然也可以是无向图)。与Dijkstra相比的优点是,也适合存在负权的图。

    若存在最短路(不含负环时),可用Bellman-Ford求出,若最短路不存在时,Bellman-Ford只能用来判断是否存在负环。

    松弛:  

      每次松弛操作实际上是对相邻节点的访问(相当于广度优先搜索),第n次松弛操作保证了所有深度为n的路径最短。由于图的最短路径最长不会经过超过|V| - 1条边,所以可知贝尔曼-福特算法所得为最短路径,也可只时间复杂度为O(VE)。

    负边权操作

      与迪科斯彻算法不同的是,迪科斯彻算法的基本操作“拓展”是在深度上寻路,而“松弛”操作则是在广度上寻路,这就确定了贝尔曼-福特算法可以对负边进行操作而不会影响结果。

    负权环判定

      因为负权环可以无限制的降低总花费,所以如果发现第n次操作仍可降低花销,就一定存在负权环

    基本操作:

    1. 创建源顶点 v 到图中所有顶点的距离的集合 distSet,为图中的所有顶点指定一个距离值,初始均为 Infinite,源顶点距离为 0;
    2. 计算最短路径,执行 V - 1 次遍历;
      • 对于图中的每条边:如果起点 u 的距离 d 加上边的权值 w 小于终点 v 的距离 d,则更新终点 v 的距离值 d;
    3. 检测图中是否有负权边形成了环,遍历图中的所有边,计算 u 至 v 的距离,如果对于 v 存在更小的距离,则说明存在环(无向图不能用这种方法判断负环)

    正确性:

      Bellman-Ford 算法采用动态规划进行设计,实现的时间复杂度为 O(V*E),其中 V 为顶点数量,E 为边的数量。简单的说我们用

    dis[k][v]表示经过前i个顶点到达v的最短路,易得转移方程dis[k][v] = min(dis[k][v],dis[ k -1][u] + w)。未使用滚动数组优化空间时,实现的代码如下:

     1 int dis[maxv][maxv];        //dis[k][v];表示选取前k个时到达i的最短距离
     2 struct Edge
     3 {
     4     int u, v, w;
     5 }edge[maxv];
     6 int n, m;
     7 
     8 void Bellman_Ford(int s)
     9 {
    10     memset(dis, INF, sizeof(dis));
    11     for (int i = 1; i <= n; i++)  dis[i][s] = 0;
    12     for (int k = 1; k <= n - 1; k++)            
    13         for (int i = 0; i < m; i++)                
    14         {
    15             int u = edge[i].u, v = edge[i].v, w = edge[i].w;
    16             dis[k][v] = min(dis[k][v], dis[k - 1][u] + w);
    17         }
    18 }

     优化:

    循环的提前退出:

      在实际操作中,贝尔曼-福特算法经常会在未达到 |V| - 1 次前就出解,|V| -1 其实是最大值。于是可以在循环中设置判定,在某次循环不再进行松弛时,直接退出循环,进行负权环判定。

     队列优化:

      即SPFA

    二、SPFA

    是一个用于求解有向带权图单源最短路径的改良的贝尔曼-福特算法(当然也可以通过将每条边换为两条逆向的边来用于无向图)。这一算法被认为在随机的稀疏图上表现出色,并且极其适合带有负边权的图。然而SPFA在最坏情况的时间复杂度与Bellman-Ford算法相同,因此在非负边权的图中仍然最好使用Dijkstra。

    原理:

      基于Bellman-Ford之外,再可以确定,松弛操作必定只会发生在最短路径前导节点松弛成功过的节点上,用一个队列记录松弛过的节点,可以避免了冗余计算。

    优化:

      SPFA算法的性能很大程度上取决于用于松弛其他节点的备选节点的顺序。我们注意到其与Dijkstra很像,一方面,优先队列替换成普通的FIFO队列,而另一方面,一个节可以多次进入队列点。

      事实上,如果 q 是一个优先队列,则这个算法将极其类似于戴克斯特拉算法。然而尽管这一算法中并没有用到优先队列,仍有两种可用的技巧可以用来提升队列的质量,并且借此能够提高平均性能(但仍无法提高最坏情况下的性能)。两种技巧通过重新调整 q 中元素的顺序从而使得更靠近源点的节点能够被更早地处理。

    距离小者优先(Small Lable First(SLF)):

      将总是把v压入队列尾端改为比较dis[v]与dis[q.front()]的大小(为了避免出现队列为空的操作,先将v压入队尾),并且在v较小时将v压入队列的头端。

    距离大者优先(Large Lable Last(LLL)):

      我们更新队列以确保队列头端的节点的距离总小于平均,并且任何距离大于平均的节点都将被移到队列尾端。

      

     改为DFS版:

      dfs版spfa判环根据:若一个节点出现2次及以上,则存在负环。具有天然的优势。由于是负环,所以无需像一般的spfa一样初始化为极大的数,只需要初始化为0就够了(可以减少大量的搜索,但要注意最开始时for一遍)。

  • 相关阅读:
    Docker学习笔记
    SpringMVC学习笔记
    机器学习预测2022年考研成绩、考研分数线
    代码随想录贪心算法
    给定两个序列src,dst,src为入栈顺序,判断dst是否为src的一个出栈顺序(c++)
    代码随想录回溯算法
    代码随想录动态规划
    代码随想录二叉树
    Docker安装memcached
    OpenEuler镜像配置
  • 原文地址:https://www.cnblogs.com/lfri/p/9521271.html
Copyright © 2020-2023  润新知