迪杰斯特拉(Dijkstra)算法主要是针对没有负值的有向图,求解其中的单一起点到其他顶点的最短路径算法。本文主要总结迪杰斯特拉(Dijkstra)算法的原理和算法流程,最后通过程序实现在一个带权值的有向图中,选定某一个起点,求解到达其它节点的最短路径。 |
举个反例:
当我们使用迪杰斯特拉算法的时候,第一次更新路径长度为1的时候会确定 V 1 − − > V 2 V_{1}-->V_{2} V1−−>V2的最短路径,但是显然这不是最短的。
迪杰斯特拉(Dijkstra)算法是一个按照路径长度递增的次序产生最短路径的算法。主要特点是以起始点为中心按照长度递增往外层层扩展(广度优先搜索的思想),直到扩展到终点为止。
选择最短路径顶点的时候依照的是实现定义好的贪心准则,所以该算法也是贪心算法的一种。
首先,我们引进一个辅助数组Distance
,它的每个分量D[i]
表示当前所找到的从起始点v0到每个终点vi的最短路径长度。
问题一:
为什么下一条次短路径要这样选择?(贪心准则的正确性)
不失一般性,假设S为已经求得最短路径的顶点集合,下一条最短路径的终点是 x x x,利用反证法,若此路径有一条顶点不在 S S S中,则说明存在一条终点不在S而长度比此路径长度短的路径,但是这显然是矛盾的,因为我们是按照路径长度递增的次序来产生最短路径的。
所以,下一条长度次短的路径长度应该是:
D [ j ] = M i n { D [ i ] ∣ v i ∈ V − S } D[j] = Min\{D[i] | v_{i}∈V-S\} D[j]=Min{D[i]∣vi∈V−S}
其中, D i D_{i} Di或者是弧 ( v 0 , v i ) (v_{0},v_{i}) (v0,vi)上的权值,或者是 D [ k ] ( v k ∈ S ) D[k](v_{k}∈S) D[k](vk∈S)和弧 ( v k , v i ) (v_{k},v_{i}) (vk,vi)上的权值之和。
问题二:
这样产生的路径是否就是S中对应顶点的最短路径?
这也是迪杰斯特拉算法的贪心策略能取得最优解的原因。
根据上面的算法思想,我们有下面算法的实现流程:
假设用带权值的邻接矩阵arcs
来表示带权的有向图,arcs[i][j]
表示弧 < v i , v j > <v_{i},v_{j}> <vi,vj>上的权值。若 < v i , v j > <v_{i},v_{j}> <vi,vj>不存在,则置arcs[i][j]
为 ∞ ∞ ∞(计算机上允许的最大值)
可达
的最短路径长度。其中,最短路径的存储可以采用一个path
数组,存储到某结点的最短路径中该结点的前驱结点在图中的位置(起点 v 0 v_{0} v0的前驱可以设成 − 1 -1 −1)。
即若有下面这样一个带全有向图:
其邻接矩阵如下:
则迪杰斯特拉算法求解过程:
迪杰斯特拉算法:
void ShortestPath_DIJ(MGraph G, int v0, int* path, int* distance) {
/*用Dijkstra算法求v0到其余顶点的最短路径p[n]和带权长度distance[n]
* final[i]=1表示已求得v0到vi的最短路径
*/
int* final = new int[G.vexnum];
int i;
// 初始化结点
for (i = 0; i < G.vexnum; i++) {
path[i] = -1;
final[i] = 0;
distance[i] = INT_MAX;
}
distance[v0] = 0; // 初始化源点
for (i = 0; i < G.vexnum; i++) { // 寻找不同长度的最短路径
int min = INT_MAX; // 当前所知里v0顶点的最短距离
int index = -1; // 最短距离对应的下标
int j;
for (j = 0; j < G.vexnum; j++) {
if (!final[j]) { // j在V-S中
if (distance[j] < min) {
min = distance[j];
index = j;
}
}
}// 寻找最短路径
if (index == -1) break;
final[index] = 1; // 离v0最近的顶点Index并入S,第一次一定是v0
for (j = 0; j < G.vexnum; j++) { // 更新距离矩阵,一定要判断是否达,即arcs[][]!=OO,否则会整数溢出
if (!final[j] && G.arcs[index][j] != INT_MAX && (min + G.arcs[index][j] < distance[j])) {
distance[j] = min + G.arcs[index][j];
path[j] = index;
}
}
}
delete[] final;
}
完整测试用例
#include
#include
#include
using namespace std;
#define MAX_VEX_NUM 50
// 定义图的邻接矩阵存储
typedef struct MGraph {
int arcs[MAX_VEX_NUM][MAX_VEX_NUM];
string vexs[MAX_VEX_NUM];
int arcnum, vexnum;
}MGraph;
void createGraph(MGraph& G);
int locate(MGraph G, string u);
void ShortestPath_DIJ(MGraph G,int v0,int* path,int* distance);
void showMatrix(MGraph G);
void showpath(MGraph G,int* path, string u); // 打印从v0到u的路径
int main() {
MGraph G;
createGraph(G);
showMatrix(G);
int* path = new int[G.vexnum];
int* distance = new int[G.vexnum];
ShortestPath_DIJ(G, 0,path,distance);
cout << "最后的路径数组:\n";
cout << "V0--V1:"; showpath(G, path, "V1");
cout << "V0--V2:"; showpath(G, path, "V2");
cout << "V0--V3:"; showpath(G, path, "V3");
cout << "V0--V4:"; showpath(G, path, "V4");
cout << "V0--V5:"; showpath(G, path, "V5");
system("pause");
return 0;
}
void showpath(MGraph G,int* path, string u) {
stack<int> stk;
int v = locate(G, u);
stk.push(v);
int parent = path[v];
if (parent == -1) cout << "V0到" << u << "没有路径";
while (parent != -1) {
stk.push(parent);
parent = path[parent];
}
while (!stk.empty()) {
int index = stk.top(); stk.pop();
cout << G.vexs[index];
if (!stk.empty()) cout << "-->";
}
cout << endl;
}
void showMatrix(MGraph G) {
cout << "图的邻接矩阵:\n";
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
if (G.arcs[i][j] == INT_MAX)
cout << "oo" << " ";
else
cout << G.arcs[i][j] << " ";
}
cout << endl;
}
}
void createGraph(MGraph& G) {
cout << "输入图的顶点数和边数:\n";
cin >> G.vexnum >> G.arcnum;
cout << "输入图的顶点信息:\n";
int i;
for (i = 0; i < G.vexnum; i++) cin >> G.vexs[i];
// 初始化邻接矩阵
int j;
for (i = 0; i < G.vexnum; i++) {
for (j = 0; j < G.vexnum; j++)
G.arcs[i][j] = INT_MAX;
}
cout << "输入图的边的权值信息vi vj weight:\n";
for (i = 0; i < G.arcnum; i++) {
string v1, v2;
int weight;
cin >> v1 >> v2 >> weight;
int l1 = locate(G, v1);
int l2 = locate(G, v2);
G.arcs[l1][l2] = weight;
}
}
int locate(MGraph G, string u) {
int i;
for (i = 0; i < G.vexnum && G.vexs[i] != u; i++);
if (i == G.vexnum) return -1;
else return i;
}
这里我用的是栈倒序输出,当然也有其它存储最短路径的方法。
测试用例(即上面的有向网):
6 8
V0 V1 V2 V3 V4 V5
V0 V5 100
V0 V4 30
V0 V2 10
V1 V2 5
V2 V3 50
V3 V5 10
V4 V5 60
V4 V3 20
两个FOR
循环嵌套,最后的时间复杂度为 O ( n 2 ) O(n^{2}) O(n2)。
若想要求源点到某一顶点的最短路径,也可以用Dijkstra算法,复杂度一样;
但是若要求每一对顶点间的最短路径,可以调用Dijkstra算法
n次,复杂度 O ( n 3 ) O(n^{3}) O(n3),也可以用弗洛伊德算法(Floyd),复杂度也是 O ( n 3 ) O(n^{3}) O(n3),但是形式上看起来简单一点。
《数据结构c语言版》 严蔚敏著