数据结构 | 第6章 最短路径

143 阅读3分钟

6.11 最短路径

最短路径问题:

给定带权网络G = (V, E),以及源点(source)s∈V,对于所有的其它顶点v,s到v的最短通路有多长?该通路由哪些边构成?

最短路径树:

  • 单调性:最短路径的任一前缀也是最短路径
  • 歧义性:s到v的最短路径未必唯一
  • 无环性:考查从源点到其余顶点的最短路径(若有多条,任选其一)。由以上单调性,这些路径的并集必然不含任何(有向)回路。这就意味着,它们应如图(b)和图(c)所示,构成所谓的最短路径树(shortest-path tree)。

image-20220715202721638.png

Dijkstra算法:

  • 最短路径子树序列:

    将顶点ui到起点s的距离记作:di = dist(s, ui),1 ≤ i ≤ n。不妨设di按非降序排列,即di ≤ dj当且仅当i ≤ j。于是与s自身相对应地必有:u1 = s。

    只需从Tk+1中 删 除 距 离 最 远 的 顶 点uk+1,即可将Tk+1转换至Tk,且uk+1在Tk+1中必然为叶节点。

  • 贪心迭代:

    颠倒上述思路可知,只要能够确定uk+1,便可反过来将Tk扩展为Tk+1。如此,便可按照到s距离的非降次序,逐一确定各个顶点{ u1, u2, ..., un },同时得到各棵最短路径子树,并得到最终的最短路径树T = Tn。现在,问题的关键就在于:

    如何才能高效地找到uk+1?

    实际上,由最短路径子树序列的上述性质,每一个顶点uk+1都是在Tk之外,距离s最近者。若将此距离作为各顶点的优先级数,则与最小支撑树的Prim算法类似,每次将uk+1加入Tk并将其拓展至Tk+1后,需要且只需要更新那些仍在Tk+1之外,且与Tk+1关联的顶点的优先级数。可见,该算法与Prim算法仅有一处差异:考虑的是uk+1到s的距离,而不再是其到Tk的距离。

  • 实现:

    每次由Tk扩展至Tk+1时,可将Vk之外各顶点u到Vk的距离看作u的优先级数(若u与Vk内顶点均无联边,则优先级数设为+∞)。如此,每一最短跨越边ek所对应的顶点uk,都会因拥有最小的优先级数(或等价地,最高的优先级)而被选中。

    唯一需要专门处理的是,在uk和ek加入Tk之后,应如何快速地更新Vk+1以外顶点的优先级数。实际上,只有与uk邻接的那些顶点,才有可能在此后降低优先级数。因此与Prim算法一样,也可遍历uk的每一个邻居v,只要边ukv的权重加上uk的优先级数,小于v当前的优先级数,即可将后者更新为前者(即考虑的是uk+1到s的距离,而不再是其到Tk的距离。)。

     0001 template <typename Tv, typename Te> struct DijkPU { //针对Dijkstra算法的顶点优先级更新器
     0002    virtual void operator() ( Graph<Tv, Te>* g, Rank v, Rank u ) {
     0003       if ( UNDISCOVERED == g->status ( u ) ) //对于v每一尚未被发现的邻接顶点u,按Dijkstra策略
     0004          if ( g->priority ( u ) > g->priority ( v ) + g->weight ( v, u ) ) { //做松弛
     0005             g->priority ( u ) = g->priority ( v ) + g->weight ( v, u ); //更新优先级(数)
     0006             g->parent ( u ) = v; //并同时更新父节点
     0007          }
     0008    }
     0009 };
    
  • 实例:

image-20220715210209883.png

  • 复杂度:O(n2)O(n^2)

“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 12 天,点击查看活动详情