• 单源最短路径算法——Dijkstra算法(迪杰斯特拉算法)


    一 综述

    Dijkstra算法(迪杰斯特拉算法)主要是用于求解有向图中单源最短路径问题。其本质是基于贪心策略的(具体见下文)。其基本原理如下:

    (1)初始化:集合vertex_set初始为{source_vertex},dist数组初始值为$dist[i] = G.arc[source\_vertex][i],i=0,1,ldots,n-1$

    (2)从顶点集合V-vertex_set中选出$v_j$,满足$dist[j] = Minleft{dist[i] | v_i∈V-vertex\_set ight}$,那么$v_j$就是当前求得的一条从source_vertex出发的最短路径的终点,并令$vertex\_set = vertex\_set ∪ j$。

    (3)修改从source_vertex出发到集合V-vertex_set上任一顶点$v_k$可达的最短路径长度:如果$dist[j] + arc[j][k] < dist[k]$,则令$dist[k] = dist[j] + G.arc[j][k]$。

    (4)重复(2)~(3)操作共n-1次,直到所有的顶点都包含在vertex_set中。

    具体代码如下:

    #include<iostream>
    #include<unordered_map>
    #include<queue>
    #include<cstring>
    #include<cstdlib>
    #include<cmath>
    #include<algorithm>
    #include<sstream>
    #include<set>
    #include<map>
    using namespace std;
    #define MAX_NUM 100
    #define INF 0x7fffffff
    /*
    dijkstra算法的实现
    参数说明:
    1.source_vertex:表示源点
    2.G:表示图(此处以邻接矩阵为例)
    3.dist数组:表示源点到其他所有顶点的最短路径的长度。例如dist[j]表示源点到顶点Vj的最短路径长度
    4.vertex_set数组:表示已找到最短路径的顶点的集合,其中vertex_set[i] = true表示顶点dist[i]已经最终确定
    5.pre数组:用以表示顶点的最短路径的前驱顶点。例如,pre[i] = k表示顶点vi的最短路径上的前驱顶点为vk。
    */
    class Graph
    {
        public:
        int vertexNum;//顶点个数
        int arcNum;//弧的个数
        int vertex[MAX_NUM];//顶点表
        int arc[MAX_NUM][MAX_NUM] = {{0,INF,10,INF,30,100},
        {INF,0,5,INF,INF,INF},
        {INF,INF,0,50,INF,INF},
        {INF,INF,INF,0,INF,10},
        {INF,INF,INF,20,0,60},
        {INF,INF,INF,INF,INF,0}};//弧信息表
    };
    void Dijkstra(Graph &G,int source_vertex,int dist[],bool vertex_set[],int pre[])
    {
        int _min;
        int k;
        int vertex_num = G.vertexNum;//顶点个数
        //初始化
        for(int i = 0 ; i < vertex_num; i++)
        {
            dist[i] = G.arc[source_vertex][i];//初始化dist数组
            if(i == source_vertex)
                vertex_set[i] = true;//将顶点source_vertex加入vertex_set数组
            else
                vertex_set[i] = false;
            pre[i] = 0;//前驱顶点为v0,后面会更新
        }
        //遍历n-1次,每次找到一个顶点的最短路径
        for(int i = 1; i < vertex_num; i++)
        {
            _min = INF;//初始化辅助变量
            //在未获取最短路径的顶点中,找到离source_vertex最近的顶点vk。
            for(int j = 0; j < vertex_num; j++)
            {
                if(vertex_set[j] == false && dist[j] < _min)
                {
                    _min = dist[j];
                    k = j;
                }
    
            }
            //此时源点到顶点vk的最短距离已经找到,即dist[k]已经确定,将k加入到vertes_set中
            vertex_set[k] = true;
            //对dist[j]进行检验更新
            for(int j = 0; j < vertex_num; j++)
            {
                int tmp = (G.arc[k][j]== INF ? INF : _min + G.arc[k][j]);//防止溢出
                if(vertex_set[j] == false && tmp < dist[j])
                {
                    dist[j] = _min + G.arc[k][j];//更新满足条件的顶点的dist数组值
                    pre[j] = k;//更新前驱顶点
                }
            }
        }
    
    }
    
    int main()
    {
        Graph G;
        G.vertexNum = 6;
        int source_vertex = 0;
        int dist[6] = {0};
        bool vertex_set[6];
        int pre[100] = {0};
        Dijkstra(G,source_vertex,dist,vertex_set,pre);
        cout<<dist[0]<<endl;
    
    }
    

      

     该算法的时间的时间复杂度为O(n^3),n为图中顶点的个数。其中比较核心的部分是最里面的两个for循环,第一个for循环对应的是第二步;而第二个for循环对应的是第三步;最外层的for循环对应的是第四步。

    此外,(1)由于INF表示的int能表示的最大值,它加上一个正值必然会溢出,所以应该考虑溢出的问题。(或者别把INF设成这么大,设小些)

         (2)我们默认带权有向图在表示时,若果i == j,则w(i,j)=0而不是w(i,j)=∞。

    二 相关理论和注意事项

    1.Dijkstra算法的核心之处在于:从源点$v_0$到目标顶点$v_j$的最短路径,要么是弧$(v_0,v_j)$;要么是中间只经过vertex_set中的顶点而最后达到顶点$v_j$的路径。

    证明如下:假设符合上述结论的最短路径为L1,假设从$v_0$到顶点$v_j$的最短路径上有一个顶点不在vertex_set中,则说明存在一条终点不在vertex_set中而长度比此路径更短的路径,设该路径为L2。但是,这是与事实相矛盾的。因为我们是按路径长度递增的次序来产生个最短路径的,故长度比此路径短的所有路径都已经产生,它们的终点必然在vertex_set中;即若L2 < L1,L2中的终点必然在集合vertex_set中。

    2.更新集合vertex_set只在第二步,相当于每次选出最小的dist[j],实质就是一个贪心的过程;而第三部相当于更新每个满足条件的dist[j]。第二步和第三步就体现了理论中的两种情况。

    3.可以根据pre数组追溯到源点到目标顶点的最短路径序列。  

    4.Dijkstra算法不适用于边上带有负权重的有向图,如果边上有负值的话,有可能出现当与vertex_set内某点(记为A)以负边相连的点(记为B)确定最短路径时,它的最短路径加上负边的权值结果小于A原先确定的最短路径的长度,而此时的A在dijkstra算法下是无法更新的。例如:

    根据Dijkstra算法而言,如果求$v_0$到其他顶点的最短路径的话,首先一开始确定的是$dist[0] = 0$且$vertex\_set[0] = true$,然后由于$dist[2]$是最小的,所以$vertex\_se[2] = true$即$v_0$到$v_2$的最短路径长度为5,然而实际上$v_0$到$v_1$再到$v_2$的距离明显更小,所以实际上$v_0$到$v_2$的最短路径长度为应为7 - 5 = 2;但是$dist[2]$无法再更新了,所以此时利用Dijkstra算法求得的最短路径是错误的。

  • 相关阅读:
    java中生成流水号的一个例子(使用关系型数据库)
    润乾报表锁定表头的实现方法
    android DVM
    SAX与DOM解析XML的区别
    RunnableException与CheckedException
    android 内存管理机制、异常、垃圾回收
    单元测试
    SAP C4C url Mashup的跳转工作原理
    SAP Cloud for Customer的Mashup位于CustomPane里
    SAP C4C的URL Mashup无法添加到embedded component里去
  • 原文地址:https://www.cnblogs.com/wangkundentisy/p/9297094.html
Copyright © 2020-2023  润新知