Dijkstra算法研究(深度解析/C++实现)

1    Dijkstra 算法的介绍

Dijkstra 算法——迪科斯彻算法(Dijkstra),算法解决的是有向图中单个源点到其他顶点的最短路径问题(针对的是不含有权值为负的边)。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

说明:【Dijkstra算法能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率低】

2    Dijkstra 算法的主要思想

下面用一个实际的例子来解释Dijkstra算法的思想。

下图所示是一张没有权值为负的边的有向有权图G(DAG),求解顶点V0到图中全部顶点(V0、V1、V2、V3、V4、V5、V6)的最短路径和最短路径距离。

《Dijkstra算法研究(深度解析/C++实现)》

图 1有向有权图(DAG)

为解决此问题,我们设定集合S{}表示顶点V0到图中全部顶点(V0、V1、V2、V3、V4、V5、V6)的最短路径。设定集合D{}表示顶点V0到图中全部顶点(V0、V1、V2、V3、V4、V5、V6)的距离。

下面开始分析:

步骤1:由于图G是没有权值为负的边,所以Path[V0—>V0]一定是顶点V0的最短路径。顶点V0的出边有Edge[V0–>V1]和Edge[V0–>V3],到顶点V1和顶点V3的距离分别是2和1,此时集合S={ Path[V0—>V0]};集合D={minDis [V0]=0, dis[V1]=2,dis[V3]=1}  (说明最小距离记minDis)。

步骤2:由于图G没有权值为负的边,且Edge[V0–>V1] > Edge[V0–>V3],所以Path[V0—>V3]一定是顶点V3的最短路径(集合S={ Path[V0—>V0], Path[V0—>V3]} )。顶点V3有四条出边Edge[V3–>V2]、Edge[V3–>V4],Edge[V3–>V5]、Edge[V3–>V6],更新集合D,D={minDis [V0]=0, dis[V1]=2, dis[V2]=3, minDis[V3]=1,dis[V4]=3,dis[V5]=9,dis[V6]=5}。

步骤3:在集合D中寻找距离顶点V0最小的顶点(贪心策略)。找到顶点V1,那么Path[V0—>V1]为到顶点V1的最短路径。集合S={ Path[V0—>V0], Path[V0—>V1],Path[V0—>V3]})。顶点V1的出边有Edge[V1–>V3]、Edge[V1–>V4],由于顶点V3的最短路径和距离已知,所以不予理会,dis[V0–>V4]=dis[V0–>V1]+ dis[V0–>V4]=12<3,所以不更新集合D。此时D={minDis[V0]=0, minDis[V1]=2, dis[V2]=3,minDis [V3]=1,dis[V4]=3,dis[V5]=9,dis[V6]=5}。

步骤4:与原理和步骤3相同,找出Path[V0—>V3–>V2]为到顶点V2的最短路径。顶点V2出边Edge[V2–>V0]、Edge[V2–>V5],由于顶点V0的最短路径已知,而dis[V0–>V3 –>V5]=8因此更新集合D。此时集合S和集合D分别为:

S={ Path[V0—>V0],Path[V0—>V1], Path[V0—>V3–>V2],Path[V0—>V3]} )

D={minDis[V0]=0,minDis[V1]=2, minDis[V2]=3, minDis [V3]=1,dis[V4]=3,dis[V5]=8,dis[V6]=5}

步骤5:与原理和步骤3相同,找出Path[V0—>V3–>V4]为到顶点V4的最短路径。dis[V0–>V3 –>V4—>V6]=9>dis[V6],所以不更新集合D。此时集合S和集合D分别是:

S={ Path[V0—>V0],Path[V0—>V1], Path[V0—>V3–>V2],Path[V0—>V3]} ,Path[V0—>V3–>V4])

D={minDis[V0]=0,minDis[V1]=2, minDis[V2]=3, minDis [V3]=1,minDis[V4]=3,dis[V5]=8,dis[V6]=5}

步骤6:与原理和步骤3相同,找出Path[V0—>V3–>V6]为到顶点V6的最短路径。dis[V0–>V3 –>V4—>V5]=6<dis[V5],更新集合D。此时两个集合:

S={ Path[V0—>V0],Path[V0—>V1], Path[V0—>V3–>V2],Path[V0—>V3]} ,Path[V0—>V3–>V4],Path[V0—>V3–>V6])

D={minDis[V0]=0,minDis[V1]=2, minDis[V2]=3, minDis [V3]=1,minDis[V4]=3,dis[V5]=6,minDis[V6]=5}

步骤7:最后找出顶点V5的最短路径,Path[V0—>V3–>V6–>V5]。两个集合为:

S={ Path[V0—>V0],Path[V0—>V1], Path[V0—>V3–>V2],Path[V0—>V3]} ,Path[V0—>V3–>V4],Path[V0—>V3–>V6–>V5],Path[V0—>V3–>V6])

D={minDis[V0]=0,minDis[V1]=2, minDis[V2]=3, minDis [V3]=1,minDis[V4]=3,minDis[V5]=6,  minDis[V6]=5}

3    Dijkstra算法的实现

相信通过上一节的讨论,对Dijkstra都能有一个感性的认识。下面我们看一下《算法导论》上面关于Dijkstra算法的伪代码:

DIJKSTRA(G, w, s)
1  INITIALIZE-SINGLE-SOURCE(G, s)  //1、初始化结点工作
2  S ← Ø
3  Q ← V[G]                       //2、插入顶点操作
4  while Q ≠ Ø
5      do u ← EXTRACT-MIN(Q)   //3、从最小队列中,抽取最小点工作
6         S ← S ∪{u}
7         for each vertex v ∈ Adj[u]
8        do RELAX(u, v, w)           //4、松弛操作。

图 2 Dijkstra算法的伪代码实现

从上面的伪代码中我们可以看出Dijkstra主要分为如下四个步骤:

步骤1:初始化结点工作;

步骤2:插入结点操作;

步骤3:抽取距离源顶点最小的顶点

步骤4:松弛操作。

       接下来我们将依次讨论这四个步骤。

说明:【说明由于篇幅有限,在接下来的3.1、3.2、3.3和3.4四个小节,我将忽略图的定义、顶点的定义和边的定义。在本文最后,我会给出Dijkstra算法的基于一般链表实现、fibonacci堆C++源代码和部分测试用例,感兴趣的朋友可以自行复制下载。】

3.1 初始化结点工作

如下代码演示了Dijkstra算法的初始化工作:

1.  形参断言判断给定参数key的顶点在图中是否存在;

2.  初始化图中全部顶点的集合D和访问状态为未访问;

voidGraph::dijkstra(long key) {
    // Assert input argument.
    Vertex *nodeTmp = NULL;
    if (!findNode(key, &nodeTmp)) {
        cout<<"Cannot find the node whose key = "<<key<<endl;
        return ;
    }
    
    // For each Vertex V.
    list<Vertex*>::iterator iter = vertexSet.begin();
    for ( ; iter != vertexSet.end(); iter++ ) {
        (*iter)->setDis(INFINITY);
        (*iter)->setStatus(false);
}
//…..
}

图 3 Dijkstra算法的初始化工作

3.2  插入顶点操作

插入顶点操作比较简单,设定从该顶点出发到该顶点自身的距离为0,如下代码片段所示:

    // Initialize.
    nodeTmp->setDis(0);

图 4 插入顶点操作

3.3 抽取距离源顶点最小的顶点

抽取距离源顶点中最小的顶点的操作其实就是扫描顶点集,在未确定最短路径的顶点中找出到源顶点距离最短的顶点,不难看出顶点集就是一个优先队列。基于不同数据结构的顶点集的时间复杂度不同,下面列出三种不同数据结构实现的顶点集的时间复杂度:

基于数据结构时间复杂度
链表、数组O(V*V+ E)
二叉堆 O(V*lgV+ |E|*lgV)
斐波那契堆O(V*lgV+ E)

图 5 优先队列实现方式时间复杂度对比

源代码:

// 遍历顶点集,找出顶点集中距离源顶点最短的顶点
boolGraph::findSmallestDis(Vertex **V) {
    DistType minDis = INFINITY;
    Vertex * nodeTmp = NULL;
    DistType tmp = INFINITY;
    
    list<Vertex*>::iterator iter = vertexSet.begin();
    for ( ; iter != vertexSet.end(); iter++ ) {
        if ((*iter)->getStatus()) {
            continue;
        }
        tmp = (*iter)->getDis();
        if (minDis > tmp) {
            minDis = tmp;
            nodeTmp = *iter;
        }
    }
    
    *V = nodeTmp;
    return (NULL == nodeTmp) ? false :true;
}

图 6抽取距离源顶点中最小的顶点

说明:本文给出的代码示例是用链表实现的

3.4 松弛操作

松弛操作技术其实就是在第2节操作步骤中我们设定的集合D{},在Dijkstra算法中其原理就是遍历到一个顶点U的时候,比较weight[u–>v]+dis[v]的值与d[u],若weight[u–>v]+dis[v]小于d[u]就更新集合D{}。

首先判断此顶点是否访问过,然后松弛操作,如下代码片段所示:

            if (!W->getStatus()) {//Is known?
                // Relex
                DistType disPath = V->getDis() + E->getWeight();
                if (disPath < W->getDis()) {
                    
                    // Update W.
                    W->setDis(disPath);
                    W->setPath(V);
                }
            }

图 7 松弛操作

3.5 Dijkstra算法代码

voidGraph::dijkstra(long key) {
    // Assert input argument.
    Vertex *nodeTmp = NULL;
    if (!findNode(key, &nodeTmp)) {
        cout<<"Cannot find the node whose key = "<<key<<endl;
        return ;
    }
    
    // For each Vertex V.
    list<Vertex*>::iterator iter = vertexSet.begin();
    for ( ; iter != vertexSet.end(); iter++ ) {
        (*iter)->setDis(INFINITY);
        (*iter)->setStatus(false);
    }
    
    // Initialize.
    nodeTmp->setDis(0);
    
    for (;;) {
        // Find smallest unknown distance vertex.
        Vertex *V = NULL;
        if (!findSmallestDis(&V)) {
            cout<<"dijkstra end."<<endl;
            break;
        }
        
        // Set this point Known.
        V->setStatus(true);
        
        // For each Vertex W adjacent to V.
        Edge* E = NULL;
        if (!V->getEdgeAdj(&E)) {
            break;
        }
        Vertex *W = E->getDes();
        for (;;) {
            
            if (!W->getStatus()) {
                
                DistType disPath = V->getDis() + E->getWeight();
                if (disPath < W->getDis()) {
                    
                    // Update W.
                    W->setDis(disPath);
                    W->setPath(V);
                }
            }
            
            // Get adjacent W.
            if ( !V->getNextEdge(&E) ) {
                break;
            }
            W = E->getDes();
        }
        
        
    }
    
    return ;
}

图 8 Dijkstra算法代码

4    附录

Dijkstra源代码和头文件以及测试用例,头文件代码:

//
//  dijkstra.h
//  100-alg-tests
//
//  Created by bobkentt on 15-8-23.
//  Copyright (c) 2015年 kedong. All rights reserved.
//

#ifndef ___00_alg_tests__dijkstra__
#define ___00_alg_tests__dijkstra__

#include <stdio.h>
#include <stdio.h>
#include <list>
#define INFINITY 0XFFFF
typedef int DistType;
typedef class Vertex _Vertex;
class Edge {
    int weight;         /* 边的权值 */
    _Vertex * ori;  	/* 弧的起点*/
    _Vertex * des;  	/* 弧的终点*/
public:
    
    Edge(int _weight,_Vertex *_ori,_Vertex *_des)
    : weight(_weight),ori(_ori),des(_des) {  };
    ~Edge() {};
    // 获取弧的起点
    _Vertex *getOri() {return ori;};
    
    // 获取弧的终点
    _Vertex *getDes() {return des;};
    
    // 获取弧的权值
    int getWeight() {return weight;};
    
};
class Vertex {
    long key;
    std::list<Edge*> adj;
    std::list<Edge*>::iterator iter;
    bool known;
    DistType dist;
    Vertex *path;
public:
    Vertex(long _key);
    ~Vertex();
    
    long getKey();
    
    void addEdge(Edge* _edge);
    
    bool getStatus();
    
    void setStatus(bool status);
    
    DistType getDis();
    
    void setDis(DistType dis);
    
    Vertex *getPath();
    
    void setPath(Vertex *node);
    
    bool getEdgeAdj(Edge **header);
    
    bool getNextEdge(Edge** edge);
};
class Graph {
    std::list<Vertex*> vertexSet;
    
public:
    Graph() {};
    ~Graph() {};
    
    Vertex* addNode(long key,int value);
    
    void addEdge(long keyOri,long keyDes,int weight);
    
    bool findNode(long key,Vertex **node);
    
    void dijkstra(long key);
    
    bool findSmallestDis(Vertex **V);
    
    void printPath(Vertex *V);
    
    void printDis();
    
    void printNode();
};

int testGraphDijkstra();


#endif /* defined(___00_alg_tests__dijkstra__) */

源文件:

//
//  dijkstra.cpp
//  100-alg-tests
//
//  Created by bobkentt on 15-8-23.
//  Copyright (c) 2015年 kedong. All rights reserved.
//

#include <iostream>
#include <list>

#include "dijkstra.h"

using namespace std;


Vertex::Vertex(long _key) {
    key = _key;
    iter = adj.begin();
}

Vertex::~Vertex() {
    
}

long Vertex::getKey() {
    return key;
}

void Vertex::addEdge(Edge* _edge) {
    adj.push_back(_edge);
    return ;
}

bool Vertex::getStatus() {
    return known;
}

void Vertex::setStatus(bool status) {
    known = status;
    return ;
}

DistType Vertex::getDis() {
    return dist;
}

void Vertex::setDis(DistType dis) {
    dist = dis;
    return ;
}

Vertex* Vertex::getPath() {
    return path;
}

void Vertex::setPath(Vertex *node) {
    path = node;
    return ;
}

bool Vertex::getEdgeAdj(Edge **header) {
    *header = adj.front();
    
    return (adj.size() == 0) ? false : true;
}

bool Vertex::getNextEdge(Edge** edge) {
    iter++;
    if (iter == adj.end()) {
        *edge = NULL;
        return false;
    }
    *edge = *iter;
    return true;
}

bool Graph::findNode(long key,Vertex **node) {
    list<Vertex*> &VS = vertexSet;
    list<Vertex*>::iterator end = VS.end();
    list<Vertex*>::iterator it;
    Vertex *nodeTmp = NULL;
    
    // 遍历顶点集,找到开始结点A
    for (it = VS.begin(); it != end; it++) {
        nodeTmp = *it;
        if (nodeTmp->getKey() == key)
        {
            break;
        }
    }
    if (it == end) {
        cout<<"graph::isNodeExist cannot find key = "<<key<<"in graph."<<endl;
        node = NULL;
        return false;
    }
    *node = nodeTmp;
    return true;
}

// 遍历顶点集,找出顶点集中距离最短的顶点
bool Graph::findSmallestDis(Vertex **V) {
    DistType minDis = INFINITY;
    Vertex * nodeTmp = NULL;
    DistType tmp = INFINITY;
    
    list<Vertex*>::iterator iter = vertexSet.begin();
    for ( ; iter != vertexSet.end(); iter++ ) {
        if ((*iter)->getStatus()) {
            continue;
        }
        tmp = (*iter)->getDis();
        if (minDis > tmp) {
            minDis = tmp;
            nodeTmp = *iter;
        }
    }
    
    *V = nodeTmp;
    return (NULL == nodeTmp) ? false :true;
}

void Graph::dijkstra(long key) {
    // Assert input argument.
    Vertex *nodeTmp = NULL;
    if (!findNode(key, &nodeTmp)) {
        cout<<"Cannot find the node whose key = "<<key<<endl;
        return ;
    }
    
    // For each Vertex V.
    list<Vertex*>::iterator iter = vertexSet.begin();
    for ( ; iter != vertexSet.end(); iter++ ) {
        (*iter)->setDis(INFINITY);
        (*iter)->setStatus(false);
    }
    
    // Initialize.
    nodeTmp->setDis(0);
    
    for (;;) {
        // Find smallest unknown distance vertex.
        Vertex *V = NULL;
        if (!findSmallestDis(&V)) {
            cout<<"dijkstra end."<<endl;
            break;
        }
        
        // Set this point Known.
        V->setStatus(true);
        
        // For each Vertex W adjacent to V.
        Edge* E = NULL;
        if (!V->getEdgeAdj(&E)) {
            break;
        }
        Vertex *W = E->getDes();
        for (;;) {
            
            if (!W->getStatus()) {
                
                DistType disPath = V->getDis() + E->getWeight();
                if (disPath < W->getDis()) {
                    
                    // Update W.
                    W->setDis(disPath);
                    W->setPath(V);
                }
            }
            
            // Get adjacent W.
            if ( !V->getNextEdge(&E) ) {
                break;
            }
            W = E->getDes();
        }
        
        
    }
    
    return ;
}

void Graph::printPath(Vertex *V) {
    if (V->getPath() != NULL) {
        printPath(V->getPath());
        cout<<" --> ";
    }
    cout<<"V"<<V->getKey();
}

void Graph::printDis() {
    // For each Vertex V.
    list<Vertex*>::iterator iter = vertexSet.begin();
    for ( ; iter != vertexSet.end(); iter++ ) {
        long key = (*iter)->getKey();
        DistType dis = (*iter)->getDis();
        cout<<"The distence of V"<<key<<" is "<<dis<<endl;
    }
    return ;
}

void Graph::printNode() {
    list<Vertex*>::iterator it = vertexSet.begin();
    cout<<"The nodes of Graph's keys = ";
    for (; it != vertexSet.end(); it++) {
        cout<<(*it)->getKey()<<" ";
    }
    cout<<endl;
    return ;
}

Vertex* Graph::addNode(long key,int value) {
    Vertex * node = new Vertex(key);
    
    vertexSet.push_back(node);
    
    return node;
}

void Graph::addEdge(long keyOri,long keyDes,int weight) {
    Vertex *ori = NULL;
    Vertex *des = NULL;
    
    // 在图中查找这两个顶点
    if (!findNode(keyOri, &ori) || !findNode(keyDes, &des)) {
        cout<<"Graph::addEdge failed:未找到该顶点"<<endl;
        
        exit(-1);
    }
    
    // 创建此弧
    Edge * edge = new Edge(weight,ori,des);
    
    // 在图中弧的起点的邻接表中,添加此弧
    ori->addEdge(edge);
    
    return ;
}


int testGraphDijkstra() {
    Vertex * N[7];
    Graph G;
    // 画出图中所有的点
    for (int i = 0; i <= 6; i++) {
        N[i] = G.addNode(i, i);
    }
    G.printNode();
    
    // 画出图中所有的边
    G.addEdge(0, 1, 2);/* V0-->V1 */
    G.addEdge(0, 3, 1);/* V0-->V3 */
    G.addEdge(1, 3, 3);/* V1-->V3 */
    G.addEdge(1, 4, 10);/* V1-->V4 */
    G.addEdge(2, 0, 4);/* V2-->V0 */
    G.addEdge(2, 5, 5);/* V2-->V5 */
    G.addEdge(3, 2, 2);/* V3-->V2 */
    G.addEdge(3, 4, 2);/* V3-->V4 */
    G.addEdge(3, 5, 8);/* V3-->V5 */
    G.addEdge(3, 6, 4);/* V3-->V6 */
    G.addEdge(4, 6, 6);/* V4-->V6 */
    G.addEdge(6, 5, 1);/* V6-->V5 */
    
    G.dijkstra(0);
    cout<<"For each print the distence:"<<endl;
    G.printDis();

    cout<<"For each print the path:"<<endl;
    for (int i = 0; i <= 6; i++) {
        G.printPath(N[i]);
        cout<<endl;
    }
    
    return 0;
}

测试用例

#include "dijkstra.h"

int main(int argc, const char * argv[]) {

    testGraphDijkstra();
    return 0;
}

打印输出:

The nodes of Graph's keys = 0 1 2 3 4 5 6 
For each print the distence:
The distence of V0 is 0
The distence of V1 is 2
The distence of V2 is 3
The distence of V3 is 1
The distence of V4 is 3
The distence of V5 is 6
The distence of V6 is 5
For each print the path:
V0
V0 --> V1
V0 --> V3 --> V2
V0 --> V3
V0 --> V3 --> V4
V0 --> V3 --> V6 --> V5
V0 --> V3 --> V6
Program ended with exit code: 0

okay,本文结束

结束语:路漫漫其修远兮,吾将上下而求索。热烈庆祝抗战胜利70周年

    原文作者:Dijkstra算法
    原文地址: https://blog.csdn.net/bobkentblog/article/details/48184597
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞