【C++】【最短路径】Dijkstra 算法实现(迪杰斯特拉算法)【最小索引堆】


一、Dijkstra 算法 是什么?

是从一个顶点到其余各顶点的最短路径算法。

解决的是有权图中最短路径问题。

迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。

1.特点

Dijkstra 不支持有负权值边的存在。

算法时间复杂度:O(ElogV) 其中E为边数、V为节点数。

算法的核心其实是relaxation,松弛操作,这对于有权图来说,十分重要。

因为两个节点中的最短路径,在有权途中,绕一圈反而比直接连接更优。

1.思想

a. 遍历节点所连接的节点,找到没有访问的节点中,能够以最短的方式到达的点,那么从源点到这个节点的最短路径就是这个路径。将其标记(下图1 节点 2 )。

b. 然后开始relaxation:遍历2的临边,发现2+1<5;即更新0->1的路径长度(图2 节点1、3).

c. 重复a.操作,确定了0->1 的最短路径为3,并进行标记。(因为这个边最小,从其他节点绕一圈肯定比这个路径要长。)然后根据节点1的临边,进行b.的操作,更新4节点权: 4(从0-1已经确定是3,3到4的权值为1,所以更新0-4路径权值为4)

d. 最终得到以0为根的最短路径树。 § 实现细节:在列表中没有访问的节点种中,找到最短路径的节点;更新列表;

复杂度分析:

用最小索引堆,开节点V个数个空间。

所以每次插入、更新都是logV 的复杂度,然后遍历所有边是E。 所以最终的时间复杂度是O(ElogV)

在这里插入图片描述

二、简易实现

bobo老师的实现代码解读::

//代码涉及:(文末参考中由相关简介)

模板类、模板函数
最小索引堆

1.解读

算法初始化过程:

distTo 为存储从source到各个节点的最短路径。初始化为节点个数个空数组。用模板类型的默认构造来初始化。

marked 为标记当前节点是否遍历过了。初始化为节点个数个的空数组。

from 为了记录最短路径中,当前节点是从哪个边过来的。可以恢复整条路经出来。

        this->s = s;
        distTo = new Weight[G.V()];
        marked = new bool[G.V()];
        for( int i = 0 ; i < G.V() ; i ++ ){
            distTo[i] = Weight();
            marked[i] = false;
            from.push_back(NULL);
        }

Dijkstra 实现过程:

  1. 对起始点进行初始化,标记source 到source 的最短路径0 、相应的边0 、并将这个边插入最小堆中。
  2. 提取最小堆中,最小权值的索引。将标定为新进入最短路径的节点;(上述一、思想a、中有讲到贪心的过程)
  3. 开始遍历当前节点v的临边e。如果当前节点v的临边e的另外一个节点w还没有被确认找到最短路径。
  4. 如果这个w节点没有访问过,或者这个w节点访问过,但是经过当前节点v再到节点w的距离更短(relaxation ),就更新这个途径节点v的最短路径到distTo;并更新当前节点最短路径是经过哪个节点的,到from。
  5. 继续循环2.提取最小权值的索引。知道最小堆被取光了。

从步骤2.开始就已经是relaxation 松弛操作了,就是经过当前节点,还能不能使得当前节点的临边节点的最短路径缩短。(如上图一、思想中图所示,通过遍历节点2 的临边节点,发现经过节点2,再到节点1的距离为2+1 = 3,小于之前从0直接到1节点的距离5 )

通过这样的松弛操作,使得临边节点的最短路径慢慢更新到最短。最终贪心的认为,更新完的最短路径中最短的那条,可以确认是最短路径中的一条路径了。

同样,relaxation 针对的只是没有找到最短路径的节点。其中包含了还没有访问过的节点,和访问过的节点,但是通过当前节点绕到这个节点的距离更短的节点。(如上图中的节点1,就是通过节点2绕行后,距离更短)

       // 使用索引堆记录当前找到的到达每个顶点的最短距离
        IndexMinHeap<Weight> ipq(G.V());
        // 对于其实点s进行初始化
        distTo[s] = Weight();
        from[s] = new Edge<Weight>(s, s, Weight());
        ipq.insert(s, distTo[s] );
        marked[s] = true;
        while( !ipq.isEmpty() ){
            int v = ipq.extractMinIndex();

            // distTo[v]就是s到v的最短距离
            marked[v] = true;

            // 对v的所有相邻节点进行更新
            typename Graph::adjIterator adj(G, v);
            for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() ){
                int w = e->other(v);
                // 如果从s点到w点的最短路径还没有找到
                if( !marked[w] ){
                    // 如果w点以前没有访问过,
                    // 或者访问过, 但是通过当前的v点到w点距离更短, 则进行更新
                    if( from[w] == NULL || distTo[v] + e->wt() < distTo[w] ){
                        distTo[w] = distTo[v] + e->wt();
                        from[w] = e;
                        if( ipq.contain(w) )
                            ipq.change(w, distTo[w] );
                        else
                            ipq.insert(w, distTo[w] );
                    }
                }
            }
        }

2.完整代码

来自bobo老师github,文末有连接。

// Dijkstra算法求最短路径
template<typename Graph, typename Weight>
class Dijkstra{

private:
    Graph &G;                   // 图的引用
    int s;                      // 起始点
    Weight *distTo;             // distTo[i]存储从起始点s到i的最短路径长度
    bool *marked;               // 标记数组, 在算法运行过程中标记节点i是否被访问
    vector<Edge<Weight>*> from; // from[i]记录最短路径中, 到达i点的边是哪一条
                                // 可以用来恢复整个最短路径

public:
    // 构造函数, 使用Dijkstra算法求最短路径
    Dijkstra(Graph &graph, int s):G(graph){

        // 算法初始化
        assert( s >= 0 && s < G.V() );
        this->s = s;
        distTo = new Weight[G.V()];
        marked = new bool[G.V()];
        for( int i = 0 ; i < G.V() ; i ++ ){
            distTo[i] = Weight();
            marked[i] = false;
            from.push_back(NULL);
        }

        // 使用索引堆记录当前找到的到达每个顶点的最短距离
        IndexMinHeap<Weight> ipq(G.V());

        // 对于其实点s进行初始化
        distTo[s] = Weight();
        from[s] = new Edge<Weight>(s, s, Weight());
        ipq.insert(s, distTo[s] );
        marked[s] = true;
        while( !ipq.isEmpty() ){
            int v = ipq.extractMinIndex();

            // distTo[v]就是s到v的最短距离
            marked[v] = true;

            // 对v的所有相邻节点进行更新
            typename Graph::adjIterator adj(G, v);
            for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() ){
                int w = e->other(v);
                // 如果从s点到w点的最短路径还没有找到
                if( !marked[w] ){
                    // 如果w点以前没有访问过,
                    // 或者访问过, 但是通过当前的v点到w点距离更短, 则进行更新
                    if( from[w] == NULL || distTo[v] + e->wt() < distTo[w] ){
                        distTo[w] = distTo[v] + e->wt();
                        from[w] = e;
                        if( ipq.contain(w) )
                            ipq.change(w, distTo[w] );
                        else
                            ipq.insert(w, distTo[w] );
                    }
                }
            }
        }
    }

    // 析构函数
    ~Dijkstra(){
        delete[] distTo;
        delete[] marked;
        delete from[0];
    }

    // 返回从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;
        }
    }
};

参考

模板类设计
[最小堆]
[最小索引堆]
bobo老师课程
bobo老师github

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值