解决单源最短路径的一个常用算法叫做:Dijkstra算法,这是一个非常经典的贪心算法例子。
注意:这个算法只对权值非负情况有效。
在每个阶段,Dijkstra算法选择一个顶点v,它在所有unknown顶点中具有最小的distance,同时算法将起点s到v的最短路径声明为known。
这个算法的本质就是 给定起点,然后假设你有一个点集(known点集),对这个点集中的点,我们已经求出起点到其的最短距离。然后慢慢扩张这个集合。直到某一时刻它包括目标点。
具体概念详细介绍见书吧,下面结合一个例子,阐述一下该算法遍历顶点的过程。
假设开始顶点s为v1。
则,第一个选择的顶点是v1,路径长是0,该顶点标记为known,
1: known(v1) unknown(v2,v3,v4,v5,v6,v7); 这时由known点集扩充,v4 为下一个最短路径顶点,路径长为1,标记v4为known
2: known(v1,v4) unknown(v2,v3,v5,v6,v7); 这时由known点集扩充,v2 为下一个最短路径顶点,路径长为2,标记v2为known
3: known(v1,v4,v2) unknown(v3,v5,v6,v7); 这时由known点集扩充,v3,v5 为下一个最短路径顶点,路径长为3, 标记v3,v5为known
4: known(v1,v4,v2,v3,v5) unknown(v6,v7); 这时由known点集扩充,v7 为下一个最短路径顶点,路径长为5, 标记v7为known
5: known(v1,v4,v2,v3,v5,v7) unknown(v6); 这时由known点集扩充,v6 为下一个最短路径顶点,路径长为6, 标记v6为known
伪代码:
struct VertexNode
{
char data[2];
int distance;
bool known;
VertexNode* path;
adjVertexNode* list;
};
void
Dijkstra(Graph& g, VertexNode& s)
{
for each vertex v in g
{
v.distance = INFINITY;
v.known = false;
v.path = NULL;
}
s.distance = 0;
for (int i=0; i<g.vertexnum; i++)
{
vertex v = smallest unknown distance vertex;
if (v==NULL)
break;
else
v.known = true;
for each w adjacent to v
{
if (!w.known)
{
if (v.distance + weight(v,w) < w.distance)
{
w.distance = v.distance + weight(v,w);
w.path = v;
}
}
}
}
}
void PrintPath(
Graph& g, VertexNode* target)
{// print the shortest path from s to target
if(target.path!=NULL)
{
PrintPath(g, target.path);
cout << " " ;
}
cout << target ;
}
代码实现:
#include <iostream>
using
namespace
std;
#define MAX_VERTEX_NUM 20
#define INFINITY 2147483647
struct
adjVertexNode
{
int
adjVertexPosition;
int
weight;
adjVertexNode
*
next;
};
struct
VertexNode
{
char
data
[
2
];
int
distance;
bool
known;
VertexNode
*
path;
adjVertexNode
*
list;
};
struct
Graph
{
VertexNode
VertexNode
[
MAX_VERTEX_NUM
];
int
vertexNum;
int
edgeNum;
};
void
CreateGraph (
Graph
&
g)
{
int
i
,
j
,
edgeStart
,
edgeEnd
,
edgeWeight;
adjVertexNode
*
adjNode;
cout
<<
"Please input vertex and edge num (vnum enum):"
<<
endl;
cin
>>
g
.
vertexNum
>>
g
.
edgeNum;
cout
<<
"Please input vertex information (v1)
/n
note: every vertex info end with Enter"
<<
endl;
for (
i
=
0;
i
<
g
.
vertexNum;
i
++)
{
cin
>>
g
.
VertexNode
[
i
].
data;
// vertex data info.
g
.
VertexNode
[
i
].
list
=
NULL;
}
cout
<<
"input edge information(start end weight):"
<<
endl;
for (
j
=
0;
j
<
g
.
edgeNum;
j
++)
{
cin
>>
edgeStart
>>
edgeEnd
>>
edgeWeight;
adjNode
=
new
adjVertexNode;
adjNode
->
weight
=
edgeWeight;
adjNode
->
adjVertexPosition
=
edgeEnd
-
1;
// because array begin from 0, so it is j-1
// 将邻接点信息插入到顶点Vi的边表头部,注意是头部!!!不是尾部。
adjNode
->
next
=
g
.
VertexNode
[
edgeStart
-
1
].
list;
g
.
VertexNode
[
edgeStart
-
1
].
list
=
adjNode;
}
}
void
PrintAdjList(
const
Graph
&
g)
{
for (
int
i
=
0;
i
<
g
.
vertexNum;
i
++)
{
cout
<<
g
.
VertexNode
[
i
].
data
<<
"->";
adjVertexNode
*
head
=
g
.
VertexNode
[
i
].
list;
if (
head
==
NULL)
cout
<<
"NULL";
while (
head
!=
NULL)
{
cout
<<
head
->
adjVertexPosition
+
1
<<
" ";
head
=
head
->
next;
}
cout
<<
endl;
}
}
void
DeleteGraph(
Graph
&
g)
{
for (
int
i
=
0;
i
<
g
.
vertexNum;
i
++)
{
adjVertexNode
*
tmp
=
NULL;
while(
g
.
VertexNode
[
i
].
list
!=
NULL)
{
tmp
=
g
.
VertexNode
[
i
].
list;
g
.
VertexNode
[
i
].
list
=
g
.
VertexNode
[
i
].
list
->
next;
delete
tmp;
tmp
=
NULL;
}
}
}
VertexNode
*
FindSmallestVertex(
Graph
&
g)
{
int
smallest
=
INFINITY;
VertexNode
* sp
=
NULL;
for (
int
i
=
0;
i
<
g
.
vertexNum;
i
++)
{
if (
!
g
.
VertexNode
[
i
].
known
&&
g
.
VertexNode
[
i
].
distance
<
smallest)
{
smallest
=
g
.
VertexNode
[
i
].
distance;
sp
=
&(
g
.
VertexNode
[
i
]);
}
}
return sp;
}
void
Dijkstra(
Graph
&
g
,
VertexNode
& s)
{
int
i;
for (
i
=
0;
i
<
g
.
vertexNum;
i
++)
{
g
.
VertexNode
[
i
].
known
=
false;
g
.
VertexNode
[
i
].
distance
=
INFINITY;
g
.
VertexNode
[
i
].
path
=
NULL;
}
s
.
distance
=
0;
for(
i
=
0;
i
<
g
.
vertexNum;
i
++)
{
VertexNode
*
v
=
FindSmallestVertex(
g);
if(
v
==
NULL)
break;
v
->
known
=
true;
adjVertexNode
*
head
=
v
->
list;
while (
head
!=
NULL)
{
VertexNode
*
w
=
&
g
.
VertexNode
[
head
->
adjVertexPosition
];
if(
!(
w
->
known) )
{
if(
v
->
distance
+
head
->
weight
<
w
->
distance)
{
w
->
distance
=
v
->
distance
+
head
->
weight;
w
->
path
=
v;
}
}
head
=
head
->
next;
}
}
void
PrintPath(
Graph
&
g
,
VertexNode
*
source
,
VertexNode
*
target)
{
if (
source
!=
target
&&
target
->
path
==
NULL)
{
cout
<<
"There is no shortest path from "
<<
source
->
data
<<
" to "
<<
target
->
data
<<
endl;
}
else
{
if (
target
->
path
!=
NULL)
{
PrintPath(
g
,
source
,
target
->
path);
cout
<<
" ";
}
cout
<<
target
->
data ;
}
}
int
main(
int
argc
,
const
char
**
argv)
{
Graph
g;
CreateGraph(
g);
PrintAdjList(
g);
VertexNode
&
start
=
g
.
VertexNode
[
0
];
VertexNode
&
end
=
g
.
VertexNode
[
6
];
Dijkstra(
g
,
start);
cout << "print the shortest path from v1 to v7" << endl;
PrintPath(
g
,
&
start
,
&
end);
cout << endl;
DeleteGraph(
g);
return
0;
}
运行结果:
对于这个算法,vertex v = smallest unknown distance vertex;如果用最简单的扫描数组方式实现,则该部操作时间复杂度为O(|V|), 所以该算法将花费O(|V|2)查找最小距离顶点,同时,注意到最内层循环,最终对每条边会执行一次,所以该算法时间复杂度为O(|V|2+|E|)
对于无向图,可以认为是双向的有向图,所以只要更改边的输入信息即可。
仍以上例看,有向图输入的边信息为12条边:
1 2 2
3 1 4
1 4 1
2 4 3
2 5 10
4 3 2
4 5 2
3 6 5
4 6 8
4 7 4
5 7 6
7 6 1
如果是无相图,即双向的有向图,则应该输入24条边信息,12组对称的边信息即可:
1 2 2
2 1 2
3 1 4
1 3 4
1 4 1
4 1 1
2 4 3
4 2 3
2 5 10
5 2 10
4 3 2
3 4 2
4 5 2
5 4 2
3 6 5
6 3 5
4 6 8
6 4 8
4 7 4
7 4 4
5 7 6
7 5 6
7 6 1
6 7 1
运行结果:
即:Dijkstra算法既可以求无向图也可以求有向图最短路径。