图算法-Dijkstra算法

来源:互联网 发布:C 释放动态数组内存 编辑:程序博客网 时间:2024/06/18 12:32

Dijkstra算法解决的是带权重的有向图上单源最短路径问题,该算法要求所有边的权重都为非负值。
Dijstra算法在运行过程中维持的关键信息是一组结点集合 S 。从源结点 s 到该集合中每个结点之间的最短路径已经被找到。算法重复从结点集 V-S 中选择最短路径估计最小的结点 u ,将 u 加入到集合 S ,然后对所有从 u 发出的边进行松弛。在下面给出的实现方式中,我们使用一个最小优先队列 Q 来保存结点集合,每个结点的关键值为其 d 值。

INITIALIZE-SINGLE-SOURCE(G,s):  //图结点属性初始化    for each vertex v in G.V:        v.d = INF  // 源结点s到结点v的距离v.d初始化为无穷大        v.p = NIL  // 结点v的前驱结点初始为空    s.d = 0        // 源结点s的s.d的值初始化为0
RELAX(u,v,w):      //松弛操作    if v.d > u.d+w(u,v):      //如果一条经过结点u的路径能够使得从源结点s到结点v的最短路径        v.d = u.d + w(u,v)    //权重比当前的估计值v.d更小,则我们对v.d的值和前驱v.p的值进行更新。        v.p = u
DIJKSTRA.(G,w,s):    INITIALIZE-SINGLE-SOURCE(G,s)    S = empty    Q = G.V    while Q not empty:        u = EXTRACT-MIN(Q)        S = S U { u }        for each vertex v in G.Adj[u]:            RELAX(u,v,w)

算法第2行执行的是例行的d值和p值的初始化,第3行将集合S初始化为一个空集。算法第4行对最小优先队列Q进行初始化,将所有的结点V都放在该队列里。算法在每次执行5~9行的while循环时,第6行从Q队列中抽取结点u,第7行将该结点加入到集合S里。然后,在算法的8~9行,我们对所有结点u发出的边(u,v)进行松弛操作。如果一条经过结点u的路径能够使得从源结点s到结点v的最短路径权重比当前的估计值v.d更小,则我们对v.d的值和前驱v.p的值进行更新。注意,在算法的第3行之后,我们再不会在队列Q中插入任何结点,而每个结点从Q中被抽取的次数和加入集合S的次数均为一次,因此,算法第5~9行的while循环的执行次数刚好为|V|次,而第8~9行的for循环的执行次数则为图的边数|E|

该算法执行了三种优先队列操作来维持最小优先队列:INSERT(算法第4行所隐含的操作)、EXTRACT-MIN(算法第6行)和DECREASE-KEY(隐含在算法第9行所调用的RELAX操作中)。所以,Dijkstra算法的总运行时间依赖于最小优先队列的实现。INSERT操作执行了1次,EXTRACT-MIN操作执行了|V|次,而DECREASE-KEY操作执行了|E|次。这里考虑两种最小优先队列实现方式进行复杂度分析:二叉堆斐波那契堆

二叉堆(O((V+E)lgV)):每次EXTRACT-MIN操作的执行时间为O(lgV),一共有|V|次这样的操作。INSERT操作的成本为O(V)。每次DECREASE-KEY的操作的执行时间为O(lgV),而最多有|E|次这样的操作。

斐波那契堆(O(VlgV+E)):每次EXTRACT-MIN操作的摊还代价为O(lgV),每次DECREASE-KEY操作的摊还代价为O(1)。从历史的角度上看,斐波那契堆提出的动机就是因为人们观察到Dijkstra算法调用的DECREASE-KEY操作通常比EXTRACT-MIN操作更多,因此任何能够次DECREASE-KEY操作的摊还代价降低到O(lgV)而不增加EXTRACT-MIN操作的摊还代价的方法都将产生比二叉堆的渐近性能更优的实现。

0 0