大漠知秋的加油站

  • 首页
大漠知秋的加油站
你就当我的烂笔头吧
  1. 首页
  2. C++
  3. 正文

最短路径 - dijkstra

2022年7月7日 571点热度 0人点赞 0条评论

简介

迪杰斯特拉算法(Dijkstra) 是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。

有权图 有向图

注意,Dijkstra 算法无法处理负权边的情况,比如 0 到 2 点的距离为:-2。

松弛操作(Relaxation)

何为松弛操作,文字描述比较抽象,看图吧。

0 到邻点

0 的邻点有 1、2、3,距离分别是 5、2、6,这其中,距离最短的是 2,到点 2,那么就可以推断出 0 到 2 的距离肯定是最短的。为什么?假设还有一条边从 1 到 2 的。

1 - 2

路径 0 - 1 的距离是 5,就光这一条边的距离已经超过了 0 - 2 的距离 2 了,再从 1 连接到 2,距离是 1,那总距离是 6,肯定没有 0 - 2 的距离短。那 0 - 3 - 2 就更不用说了,0 - 3 的距离已经是 6 了。

注意:以上都是不存在负权边的情况,假设 1 - 2 的距离是 -4,那就不一样了。Dijkstra 不能处理负权边。

松弛操作

上边已经找到 0 - 2 是第一次查找的最短路径,开始以 2 为基础进行松弛操作。

遍历点 2 的邻点,查看以点 2 为基础距离(现在到点 2 的基础距离为 2),再加上点 2 到其邻点的距离,是否比原来已知的能到达邻点的距离短。如下图:

以点 2 为基础

  • 2 - 1 的距离为 1,再加上点 2 的基础距离 2,得出 0 - 2 - 1 的总距离为:3。
  • 原到点 1 的距离是 0 - 1 的距离 5,显然此次松弛操作后,0 - 2 - 1 的距离是更短的,把到点 1 的距离更新为:3。
  • 2 - 3 的距离为 3,再加上点 2 的基础距离 2,得出 0 - 2 - 3 的总距离为:5。
  • 原到点 3 的距离是 0 - 1 的距离 6,显然此次松弛操作后,0 - 2 - 3 的距离是更短的,把到点 1 的距离更新为:5。
  • ......

总结来说就是找到一个已经确定了为最短路径的点,这里就是点 2,以其为基础距离,看其到邻点的距离是否更优。更优则更新,否则跳过。

实现

0 到其他点的最短距离动画演示

先以 0 为起点到其他所有点的最短距离观察一下。

0 到其他点的最短距离

代码实现

#include <iostream>
#include <vector>
#include <stack>
#include "../help/Edge.h"
#include "IndexMinHeap.h"

/**
 * Dijkstra 算法求最短路径
 *
 * 时间复杂度:O(Elog(V))
 */
template<typename Graph, typename Weight>
class Dijkstra {
private:
    /**
     * 图
     */
    Graph &G;
    /**
     * 起始点
     */
    int s;
    /**
     * 存放已探索到的点,从 s 到这些点的距离
     */
    Weight *distTo;
    /**
     * 点是否已经找到了最短路径
     */
    bool *marked;
    /**
     * 记录当前点是从那个点过来的
     * 通过此字段可以反推 s 到任一点的最短路径
     */
    vector<Edge<Weight> *> from;
    /**
     * 在松弛过后用来获取已探索路径中最短的点
     */
    IndexMinHeap<Weight> ipq;

public:
    Dijkstra(Graph &graph, int s) : G(graph), ipq(IndexMinHeap<Weight>(graph.v())) {
        assert( s >= 0 && s < G.v() );

        // 初始化成员变量
        this->s = s;
        distTo = new Weight[G.v()];
        marked = new bool[G.v()];
        for (int i = 0; i < graph.v(); ++i) {
            distTo[i] = Weight();
            marked[i] = false;
            from.push_back(nullptr);
        }

        // 因为从 s 点开始,s 点的就可以固定初始化一下
        // 到 s 点的上一个点也是 s 自己。
        from[s] = new Edge<Weight>(s, s, Weight());
        // 把 s 放入已探索最小索引堆,等待取出
        ipq.insert(s, Weight());

        while (!ipq.isEmpty()) {
            Weight v = ipq.extractMinIndex();
            // 新取出的已探索最短距离点必然是最短路径
            marked[v] = true;

            // 遍历 v 的邻边进行松弛操作。
            typename Graph::AdjIterator adjIter(G, v);
            for (Edge<Weight> *e = adjIter.begin(); !adjIter.end(); e = adjIter.next()) {
                // 获取当前这条边与 v 相连的点
                int w = e->other(v);

                // 点 w 没有被标记过"已经找到最短历经"的情况下再继续
                if (!marked[w]) {
                    // 如果点 w 没有被探索过 || 已经被探索过,但是新的路径距离更短
                    if (nullptr == from[w] || distTo[v] + e->wt() < distTo[w]) {
                        from[w] = e;
                        distTo[w] = distTo[v] + e->wt();

                        if (ipq.contain(w)) {
                            // 更新索引堆中的路径距离
                            ipq.change(w, distTo[w]);
                        } else {
                            // 新增
                            ipq.insert(w, distTo[w]);
                        }
                    }
                }
            }
        }
    }

    ~Dijkstra() {
        delete[] distTo;
        delete[] marked;
        delete from[s];
    }

    /**
     * 返回从 s 点到 w 点的最短路径长度
     */
    Weight shortestPathTo(int w) {
        assert(w >= 0 && w < G.v());
        assert(hasPathTo(w));
        return distTo[w];
    }

    /**
     * 判断从 s 点到 w 点是否联通
     */
    bool hasPathTo(int w) {
        assert(w >= 0 && w < G.v());
        return marked[w];
    }

    /**
     * 寻找从 s 到 w 的最短路径, 将整个路径经过的边存放在 vec 中
     */
    void shortestPath(int w, vector<Edge<Weight>> &vec) {

        assert(w >= 0 && w < G.v());
        assert(hasPathTo(w));

        // 通过from数组逆向查找到从s到w的路径, 存放到栈中
        stack<Edge<Weight> *> s;
        Edge<Weight> *e = from[w];
        while (e->v() != this->s) {
            s.push(e);
            e = from[e->v()];
        }
        s.push(e);

        // 从栈中依次取出元素, 获得顺序的从s到w的路径
        while (!s.empty()) {
            e = s.top();
            vec.push_back(*e);
            s.pop();
        }
    }

    /**
     * 打印出从 s 点到 w 点的路径
     */
    void showPath(int w) {

        assert(w >= 0 && w < G.v());
        assert(hasPathTo(w));

        vector<Edge<Weight>> vec;
        shortestPath(w, vec);
        for (int i = 0; i < vec.size(); i++) {
            cout << vec[i].v() << " -> ";
            if (i == vec.size() - 1)
                cout << vec[i].w() << endl;
        }
    }
};

测试

// testG1.txt
5 8
0 1 5
0 2 2
0 3 6
1 4 1
2 1 1
2 4 5
2 3 3
3 4 2
void testDijkstra() {
    string filename = "testG1.txt";
    int v = 5;

    SparseGraph<int> g = SparseGraph<int>(v, true);
    // Dijkstra最短路径算法同样适用于有向图
    // SparseGraph<int> g = SparseGraph<int>(v, false);
    ReadGraph<SparseGraph<int>, int> readGraph(g, filename);

    cout << "Test Dijkstra:" << endl << endl;
    Dijkstra<SparseGraph<int>, int> dij(g, 0);
    for (int i = 0; i < v; i++) {
        if (dij.hasPathTo(i)) {
            cout << "Shortest Path to " << i << " : " << dij.shortestPathTo(i) << endl;
            dij.showPath(i);
        } else
            cout << "No Path to " << i << endl;

        cout << "----------" << endl;
    }
}

show:

Test Dijkstra:

Shortest Path to 0 : 0
0 -> 0
----------
Shortest Path to 1 : 3
0 -> 2 -> 1
----------
Shortest Path to 2 : 2
0 -> 2
----------
Shortest Path to 3 : 5
0 -> 2 -> 3
----------
Shortest Path to 4 : 4
0 -> 2 -> 1 -> 4
----------
标签: C++ Dijkstra 图论 数据结构与算法
最后更新:2022年7月7日

大漠知秋

唯黄昏而思烛明,唯覆雪始念日暖,唯放手方知情真,今困苦而怀峥嵘,今飘零而涌乡愁,今孑然而徒唏嘘,唏嘘成愁。

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

文章目录
  • 简介
  • 松弛操作(Relaxation)
    • 松弛操作
  • 实现
    • 0 到其他点的最短距离动画演示
    • 代码实现
    • 测试

COPYRIGHT © 2022 大漠知秋的加油站. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang