求解单源最短路的Dijkstra算法

求解单源最短路的Dijkstra算法

Dijkstra算法1,英文为Dijkstra’s algorithm,常被译为迪杰斯特拉算法。该算法由Edsger Wybe Dijkstra2在1956年发现。Dijkstra算法使用类似BFS的方法解决带权无负权图的单源最短路问题。

算法描述

解决单源最短路,便是需要求解图 G G G上某个源点 s s s到其它点 v ∈ V − { s } v\in V- \lbrace s \rbrace vV{s}的权值和最小的路径,这里显然有两个重要求解目标——权值和、路径,这里我们先不考虑具体路径的求解。

解集dist初始化

Dijkstra算法在过程中动态维护解集 d i s t dist dist,其中 d i s t [ i ] dist[i] dist[i]为当前 s s s到编号为 i i i的点之间的最小权值和。显然在算法开始前,有
d i s t [ s ] = 0 d i s t [ v ] = ∞ dist[s]=0 \\ dist[v]=\infty dist[s]=0dist[v]=

松弛

Dijkstra的基础操作是松弛。显然对于任意时刻,如果存在一条边 w ( u , v ) w(u,v) w(u,v),使得当前 d i s t [ u ] + w e i g h t [ u ] [ v ] < d i s t [ v ] dist[u]+weight[u][v]<dist[v] dist[u]+weight[u][v]<dist[v],则可利用该边,将之前的解路径 p a t h [ v ] path[v] path[v]优化为 p a t h [ u ] → w ( u , v ) path[u]\rightarrow w(u,v) path[u]w(u,v)

贪心地进行松弛操作

算法维护两个顶点集合 S S S Q Q Q。集合 S S S保留所有已知实际最短路径值的顶点,而集合 Q Q Q则保留其他所有顶点。集合 S S S初始状态为空,而后每一步都有一个顶点从 Q Q Q移动到 S S S。这个被选择的顶点是 Q Q Q中拥有最小的 d i s t dist dist值的顶点。当一个顶点 u u u Q Q Q中转移到了 S S S中,算法对 u u u的每条邻边 w ( u , v ) w(u,v) w(u,v)进行松弛。

由于初始时 d i s t [ s ] dist[s] dist[s] 0 0 0最小,故第一轮中的松弛其实是将 d i s t [ v ] dist[v] dist[v]初始化为 w e i g h t [ s ] [ v ] weight[s][v] weight[s][v],整个算法将进行 n n n轮松弛。也有说法将第一轮松弛作为初始化的一部分,认为 n − 1 n-1 n1轮松弛即可得到结果。这是亟需辨析的点。

伪代码

《算法导论》给出了以下伪代码:
在这里插入图片描述

求解单源单汇点

上述流程中,一旦对汇点 t t t的松弛完成,则意味着最终的 d i s t [ t ] dist[t] dist[t]已经求得,此时可以终止算法。但该操作并不会降低算法的渐进复杂度,因为该汇点可能在最后一个才被选中进行松弛。
在这里插入图片描述

时间复杂度

我们发现整个算法的复杂度主要由顶点数和 E X T R A C T − M I N EXTRACT-MIN EXTRACTMIN决定。在朴素的Dijkstra算法中,我们很容易想到枚举 d i s t i dist_i disti打擂找出每轮的最小值,这一操作的复杂度为 O ( ∣ V ∣ ) O(|V|) O(V),也就是整个算法的复杂度为 O ( ∣ V ∣ 2 ) O(|V|^2) O(V2)

事实上,因为我们无差别地将当前顶点集 Q Q Q首的邻点丢进 Q Q Q,所以算法实际上遍历了每一条边,因此可以更准确地将复杂度写为 O ( ∣ V ∣ 2 + ∣ E ∣ ) O(|V|^2+|E|) O(V2+E)
但打擂也太蠢了。我们完全可以使用堆来优化 E X T R A C T − M I N EXTRACT-MIN EXTRACTMIN过程,最方便的是使用STL中的priority_queue,这种情况下 E X T R A C T − M I N EXTRACT-MIN EXTRACTMIN的复杂度降到了 O ( log ⁡ ∣ V ∣ ) O(\log |V|) O(logV),整体复杂度为 O ( ( ∣ E ∣ + ∣ V ∣ ) log ⁡ ∣ V ∣ ) O((|E|+|V|)\log |V|) O((E+V)logV)
如果使用斐波纳契堆实现优先队列,还可以将算法复杂度转为 O ( ∣ E ∣ + ∣ V ∣ log ⁡ ∣ V ∣ ) O(|E|+|V|\log |V|) O(E+VlogV),但当图为稠密图时,常数可能过大,并没有取得显著的效率提升。

正确性证明

我们考虑用数学归纳法来证明Dijkstra算法的正确性,这里仅用简单的言语描述推理过程,如有兴趣了解更多,可参见算法导论或Dijkstra本人的证明,这些内容都可以在WikiPedia3上找到。

  1. 源点入队
  2. 取队首,然后松弛,实际上也就是令 d i s t i = w e i g h t [ s ] [ i ] dist_i=weight[s][i] disti=weight[s][i],将每次松弛产生的 d i s t dist dist插入队列。
  3. 取队首,此时取出的实际上就是与 s s s直接相连的最近的点,我们且称之为 v 1 v_1 v1,我们可以证明此时 d i s t v 1 dist_{v_1} distv1已最小,原因是如果 d i s t v 1 dist_{v_1} distv1未达最小,我们就可以通过松弛操作优化它,但松弛的前提是存在点 k k k使得 d i s t [ k ] + w e i g h t [ k ] [ v 1 ] < d i s t [ v 1 ] dist[k]+weight[k][v_1]<dist[v_1] dist[k]+weight[k][v1]<dist[v1],显然,如果存在该松弛,则有 d i s t [ k ] < d i s t [ v 1 ] dist[k]<dist[v_1] dist[k]<dist[v1],这与当前取出队首为 d i s t dist dist最小点矛盾。利用 v 1 v_1 v1松弛其邻点,同样,将更新后的 d i s t dist dist插入优先队列。
  4. 同3理,可推得每一步取出的队首都应该无法再被松弛,其 d i s t dist dist已达最小值(这也是前面说到可以优化单汇求解过程的原理)。

参考实现

struct HeapNode {
    int d, u;
    bool operator < (const HeapNode& rhs) const {
        return d > rhs.d;
    }
};
struct Dijkstra {
    int n, m;
    vector<Edge> edges;
    vector<int> G[maxn];
    bool done[maxn]; //是否已永久标号
    int d[maxn]; //s到各个点的距离
    int p[maxn]; //最短路中的上一条弧
    void init(int n) {
        this->n = n;
        for (int i = 0; i < n; i++) G[i].clear();
        edges.clear();
    }

    void AddEdge(int from, int to, int dist) {
        edges.push_back(Edge(from, to, dist));
        m = edges.size();
        G[from].push_back(m - 1);
    }

    void dijkstra(int s) {
        priority_queue<HeapNode> Q;
        for (int i = 0; i < n; i++) d[i] = INF;
        d[s] = 0;
        memset(done, 0, sizeof(done));
        Q.push((HeapNode) {0, s});
        while (!Q.empty()) {
            HeapNode x = Q.top();
            Q.pop();
            int u = x.u;
            if (done[u]) continue;
            done[u] = true;
            for (int i = 0; i < G[u].size(); i++) {
                Edge &e = edges[G[u][i]];
                if (d[e.to] > d[u] + e.dist) {
                    d[e.to] = d[u] + e.dist;
                    p[e.to] = G[u][i];
                    Q.push((HeapNode) {d[e.to], e.to});
                }
            }
        }
    }
};

  1. 维基百科编者. 戴克斯特拉算法[G/OL]. 维基百科, 2020(20200630)[2020-06-30]. ↩︎

  2. 维基百科编者. 艾兹赫尔·戴克斯特拉[G/OL]. 维基百科, 2019(20190823)[2019-08-23]. ↩︎

  3. 戴克斯特拉算法#時間複雜度 ↩︎

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值