zoukankan      html  css  js  c++  java
  • 关于最短路

    关于最短路,大家应该都知道有Dijkstra,SPFA以及Floyd。

    此处先提出一个问题:

    给定图G,每条边有边权。
    求从一点到另一点的边权和最小的路径。
    要求图中不能有负回路(否则为NP问题)。

    首先提到的便是Floyd。

    如果数据范围足够小,相信大家大部分会选择Floyd(为什么呢,后文解释)。

    大家都知道Floyd板子好背,不像其它两种算法一样颇为复杂。

    但别看它貌似简单,但其实蕴含着大道理,这也是我去清北学堂的时候听hzc老师讲的。

    Floyd的储存方法就是用邻接矩阵储存,比较方便。

    它的原理是DP,时间复杂度为O(n3),空间复杂度为O(n2)。

    所以Floyd是不可以用来做数据范围较大的题的。

    令f[i][j][k]为从i到j,中途只经过编号1~k的节点的最短路。

    考虑是否经过k:

      如果经过,那么f[i][j][k]=f[i][k][k-1]+f[k][j][k-1];

      否则,f[i][j][k]=f[i][j][k-1];

    直接这样做的空间复杂度是O(n3),我们可以进一步优化。

    首先,f[..][..][k]只从f[..][..][k]转移,可以用滚动数组优化。

    进一步地,如果经过k,那么只会从fp..[[k][k-1]转移,而f[k][..][k]和f[..][k][k]必然不会这样转移,故可以直接在原数组上迭代。

    代码实现:

    f[i][j]的初始值需设为极大值,如果i与j之间有边那么f[i][j]为最小的边权。

    拓展应用:

    求图中的最小环。先考虑无向图。

    Floyd算法保证了最外层循环到|的时候所有点对之间的最短路只经过1 ∼k− 1号节点。

    环至少有3个节点,设编号最大的为x,与之直接相连的两个节点为u和v。

    环的长度应为f[u][v][x-1]+w[v][x]+w[x][u]。其中w为边权,如不存在边则为无穷大。

    我们只要进行第x次迭代之前枚举所有编号小于k的点对更新答案即可。

    代码实现:

    j只循环到i-1,因为无向图i到j与j到i是等价的。

    有向图则类似,另外注意两个点的环的情况。

    接下来我们来看SPFA(Shortest Path Faster Algorithm)

    即队列优化的Bellman-Ford算法,同样用于求单源最短路,可以用于带负权的图。

    SPFA算法:

    算法流程大致如下:

    1.记S为起点,置ds=0;对于其它点x,置dx=∞;

    2.维护一个队列,初始为空;加入S;

    3.取出队首节点,记为u;

    4.枚举所有与u相邻的节点v,用du+w(u,v)更新dv;如果dv被更新且v不在队列中,则将其加入队列;

    5.如果队列不为空,返回3;否则算法结束。

    代码实现

    算法复杂度最坏情况下是O(nm)。

    但一般来说,除非是故意卡SPFA,否则在效率上是和Dijkstra差不多的。

    关于SPFA的优化:

    1.SLF(smallest label first):如果加入的元素距离小于队首元素,则置于队首;

    2.LLL(largest label last):记录队列中的元素距离平均值,从队首取元素时,如果距离大于平均值则移至队尾,重复直到找到小于等于平均值的元素为止;

    3.随机化:从队首取元素时,有p的概率直接将其移至队尾;p应当是于图规模有关的一个小概率。

    其实这些优化不能优化复杂度,甚至可能使算法变慢,但能在一定程度上化解针对性数据。

    既然上文提到了Dijkstra,那么我们接下来自然要讲它。

    首先我们来想一下宽度优先搜索

    如果所有边权均为1,宽度优先搜索可以求出最短路。

    如果不为1?则拆点。

    用于求从图中一点出发到其它所有点的最短路。即SSSP(single source
    shortest path,单源最短路)。
    时间复杂度O(n2 ),空间复杂度O(m)(不含图结构)。
    要求所有权值非负。称这样的图为正权图。
    在下面的分析中,视1号点为源点,记du为1号点到点u的最短路距离。

    我们首先来看一些正权图的性质:
    1. 如果1到u的最短路上包含v,那么这条路径上1到v的部分为1到v的最短路。
    2. 任意一条最短路的一部分的长度一定不大于整条路径的长度。

    Dijkstra算法
    算法流程大致如下:
    1. 记S为起点,置ds = 0;对于其它点x,置dx = ∞;

    2. 找到当前尚未处理过的点中距离最小的,记为u;
    3. 枚举所有与u相邻的节点v,用du + w(u,v)更新dv ;
    4. 标记u为已处理;
    5. 如果还有尚未处理过的点,返回2;否则算法结束。

     代码实现:

    关于优化:

    首先改变储存图的方式;

    再者,我们考虑优化循环,外层循环无法优化,则内层为先找最小值,更新答案。

    我们使用堆优化。

    堆是一个支持插入元素、查询或删除最小值的数据结构。
    插入、删除复杂度为O(log n) ;
    查询复杂度为O(1) ;
    还可以支持把一个元素改小,复杂度为O(log n)。
    不需要明白具体实现,当成一个黑箱就好。

    为了优化查询最小值部分,我们需要存储所有尚未处理过的点。
    在更新时候,如果距离变小,则在堆中更新。
    而在更新时,也只在前向星中遍历从当前点发出的边。
    复杂度O(nlog n)。

    另一种写法是直接往堆里加,而不是改值,适用于STL中的优先队列。

    就不再具体实现了。

    一世安宁

  • 相关阅读:
    我的WCF之旅(1):创建一个简单的WCF程序
    网页设计中颜色的搭配
    CSS HACK:全面兼容IE6/IE7/IE8/FF的CSS HACK
    UVa 1326 Jurassic Remains
    UVa 10340 All in All
    UVa 673 Parentheses Balance
    UVa 442 Matrix Chain Multiplication
    UVa 10970 Big Chocolate
    UVa 679 Dropping Balls
    UVa 133 The Dole Queue
  • 原文地址:https://www.cnblogs.com/GTBA/p/9451454.html
Copyright © 2011-2022 走看看