1 Dijkstra 算法的介绍
Dijkstra 算法——迪科斯彻算法(Dijkstra),算法解决的是有向图中单个源点到其他顶点的最短路径问题(针对的是不含有权值为负的边)。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
说明:【Dijkstra算法能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率低】
2 Dijkstra 算法的主要思想
下面用一个实际的例子来解释Dijkstra算法的思想。
下图所示是一张没有权值为负的边的有向有权图G(DAG),求解顶点V0到图中全部顶点(V0、V1、V2、V3、V4、V5、V6)的最短路径和最短路径距离。
图 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周年