上次我们介绍了图的最小生成树算法的实现,这次介绍基于邻接矩阵的最短路径算法的实现。
还是老规矩:
程序在码云上可以下载。
地址:https://git.oschina.net/601345138/DataStructureCLanguage.git
本次最短路径共用到以下源文件,有一些已经在之前的文章介绍过。还是和以前一样,所有源文件需要放在同一目录下编译。
my_constants.h 各种状态码定义
MGraph.h 图的邻接矩阵存储结构表示定义
MGraph.cpp 基于邻接矩阵的基本操作实现
ShortestPath.cpp 最短路径算法实现
最短路径测试.cpp 主函数,调用算法完成最短路径程序的演示
邻接矩阵在《数据结构编程笔记十八:第七章 图 图的邻接矩阵存储表示及各基本操作的实现》一文有所介绍,my_constants.h、MGraph.h 和MGraph.cpp三个源文件与此文中的同名源文件内容完全一致,没有修改。这里不再重复贴了(否则文章会很长,不能突出重点),但在码云上你可以下载到全部源文件,我会把它放在一个目录下。
本次只贴最短路径的核心代码和主函数:
源文件:ShortestPath.cpp
//--------------- 最短路径 -------------------
//---------------------------迪杰斯特拉算法辅助数组的定义--------------------------------
//P[][]记录了v->v0的最短路径经过哪些路径,
//当P[i][j] = TRUE,说明i->j这样一条边在最短路径上
//当P[i][j] = FALSE,说明i->j不在最短路径上
typedef int PathMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
//D[]记录了v0到各个顶点的最短距离
typedef int ShortPathTable[MAX_VERTEX_NUM];
/* (迪斯特拉算法求单源最短路径)
函数:ShortestPath_DIJ
参数:MGraph G 图G
int v0 出发点v0
PathMatrix &P 记录最短路径经过哪些路径的P数组
ShortPathTable &D 记录v0到各个顶点的最短距离的D数组
返回值: 无
作用:用Dijkstra算法求有向图G的顶点v0顶点到其余顶点v的最短路径P[v]及其带权长度D[v]
若P[v][w]为TRUE,则w是从v0到v当前求得最小路径上的顶点
final[v]为TRUE当且仅当v∈S,即已经求得v0到v的最短路径
*/
void ShortestPath_DIJ(MGraph G, int v0, PathMatrix &P, ShortPathTable &D){ //
//临时变量
int v, w, j, min;
//final数组的作用是记录各个顶点是否已经找到最短路径
Status final[MAX_VERTEX_NUM];
//初始化数据
for(v = 0; v < G.vexnum; ++v) {
//final数组中的值为FALSE表示还未求得最短路径
final[v] = FALSE;
//将与v相邻的顶点加上权值
D[v] = G.arcs[v0][v].adj;
//设P数组的初始值为没有路径
for(w = 0; w < G.vexnum; ++w) {
P[v][w] = FALSE;
}//for
//D数组有权值即P数组对应位置存在路径
//也就是v和v0之间存在路径
if(D[v] < INFINITY){
//经过v0
P[v][v0] = TRUE;
//经过v
P[v][v] = TRUE;
}//if
}//for
//v0->v0的距离是0
D[v0] = 0;
//v0顶点并入S集
final[v0] = TRUE;
//开始主循环,每次求得v0到某个顶点v的最短路径,并加v到S集
for(int i = 0; i < G.vexnum; ++i){ //其余G.vexnum-1个顶点
//当前所知离v0顶点最近距离,设置初始值∞
min = INFINITY;
//扫描所有顶点
for(int w = 0; w < G.vexnum; ++w) {
//w顶点没有并入集合S中
if(!final[w]) {
//w顶点离v0顶点更近
if(D[w] < min){
//v记录了离v0最近的顶点
v = w;
//min记录了到v0的最短路径
min = D[w];
}//if
}//if
}//for
//离v0顶点最近的v加入S集
final[v] = TRUE;
//更新当前最短路径及距离
//根据新并入的顶点,更新不在S集的顶点到V0的距离和路径数组
for(int w = 0; w < G.vexnum; ++w) {
//修改D[w]和P[w],w∈V-S
if(!final[w] && (min + G.arcs[v][w].adj < D[w])){
//更新D[w]
D[w] = min + G.arcs[v][w].adj;
//修改P[w],v0到w经过的顶点包括v0到v经过的顶点再加上顶点w
//P[w] = P[v] + [w]
for(int j = 0; j < G.vexnum; ++j) {
P[w][j] = P[v][j];
}//for
//最短路径经过w点
P[w][w] = TRUE;
}//if
}//for
}//for
}//ShortestPath_DIJ
//---------------------弗洛伊德算法辅助数组的定义------------------------
//P[][][]记录了最短路径经过了哪些路径
typedef int PathMatrix1[MAX_VERTEX_NUM][MAX_VERTEX_NUM][MAX_VERTEX_NUM];
//D[][]记录了各顶点之间最短路径的值
typedef int DistanceMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
/* (弗洛伊德算法求各顶点之间的最短路径)
函数:ShortestPath_FLOYD
参数:MGraph G 图G
PathMatrix1 &P 记录最短路径经过哪些路径的P数组
DistanceMatrix &D 记录了各顶点之间最短路径值的D数组
返回值: 无
作用:用Floyd算法求有向网G中各对顶点v和w之间的最短路径P[v][w]及其带权长度D[v][w]。
若P[v][w][u]为TRUE,则u是从v到w当前求得最短路径上的顶点。
*/
void ShortestPath_FLOYD(MGraph G, PathMatrix1 &P, DistanceMatrix &D){
//各对结点之间的初始已知路径及距离
for(int v = 0; v < G.vexnum; ++v) {
for(int w = 0; w < G.vexnum; ++w){
//顶点v到顶点w的直接距离
D[v][w] = G.arcs[v][w].adj;
//路径矩阵的初值为FALSE
for(int u = 0; u < G.vexnum; ++u) {
P[v][w][u] = FALSE;
}//for
//从v到w有直接路径
if(D[v][w]<INFINITY){
//从v到w的路径经过v和w两点
P[v][w][v] = TRUE;
P[v][w][w] = TRUE;
}//if
}//for
}//for
//从v经u到w的一条路径更短
for(int u = 0; u < G.vexnum; ++u) {
for(int v = 0; v < G.vexnum; ++v) {
for(int w = 0; w < G.vexnum; ++w) {
//从v经u到w的一条路径更短
if(D[v][u] + D[u][w] < D[v][w]){
//更新最短距离
D[v][w] = D[v][u] + D[u][w];
//从v到w的路径经过从v到u和从u到w的所有路径
for(int i = 0; i < G.vexnum; ++i) {
P[v][w][i] = P[v][u][i] || P[u][w][i];
}//for
}//if
}//for
}//for
}//for
}//ShortestPath_FLOYD
源文件:最短路径测试.cpp
//**************************引入头文件*****************************
#include <stdio.h> //使用了标准库函数
#include <stdlib.h> //使用了动态内存分配函数
#include "my_constants.h" //引入自定义的符号常量,主要是状态码
#include "MGraph.h" //引入图的邻接矩阵表示法的基本定义
#include "MGraph.cpp" //引入图的主要操作
#include "ShortestPath.cpp" //引入图的最短路径算法实现
//----------------------主函数----------------------
int main(int argc, char *argv[]){
printf("\n-------------基于邻接矩阵的最短路径算法测试程序--------------\n\n");
//图G
MGraph G;
//临时变量,保存输入的顶点
VertexType v1, v2;
//临时变量,保存输入的顶点数
int n;
//图的创建
printf("->测试图的创建:(最短路径算法要求图必须是有向网,请选择1)\n");
CreateGraph(G);
//打印邻接矩阵
printf("\n->创建成功后图的邻接矩阵:\n\n");
PrintAdjMatrix(G);
//测试最短路径
if(G.kind == DN){
//迪杰斯特拉算法用到的临时变量:
int v0 = 0; //源点
PathMatrix P; //P数组
ShortPathTable D; //D数组
printf("\n->测试求有向网的最短路径:\n\n");
//测试迪杰斯特拉算法
printf("->使用迪杰斯特拉算法求单源最短路径:\n");
ShortestPath_DIJ(G, v0, P, D);
//打印迪杰斯特拉算法用的P数组
printf("->迪杰斯特拉算法用到的最短路径数组P[i][j]如下(T表示TRUE,F表示FALSE):\n");
for(int i = 0; i < G.vexnum; ++i) {
for(int j = 0; j < G.vexnum; ++j) {
printf(" %3c ", P[i][j] == 1 ? 'T' : 'F');
}//for
printf("\n");
}//for
//打印迪杰斯特拉算法用的D数组
printf("顶点%d到其他顶点的最短路径如下:\n", v0);
for(int i = 0; i < G.vexnum; ++i) {
printf("%d ---> %d :%3d\n", v0, G.vexs[i], D[i]);
}//for
//弗洛伊德算法用到的两个数组
//这两个数组和迪斯特拉算法的两个数组虽然同名但没有关系
PathMatrix1 P1; //P数组
DistanceMatrix D1; //D数组
//测试弗洛伊德算法
printf("->使用弗洛伊德算法求任意两顶点最短路径:\n");
//ShortestPath_FLOYD()要求对角线元素为0
for(int i = 0; i < G.vexnum; ++i) {
G.arcs[i][i].adj = 0;
}//for
printf("->执行算法前的邻接矩阵如下:\n");
PrintAdjMatrix(G);
//执行弗洛伊德算法
ShortestPath_FLOYD(G, P1, D1);
//打印弗洛伊德算法用的D数组
printf("->弗洛伊德算法用的D数组:\n\n");
//输出左上角多余的空白
printf(" ");
//输出邻接矩阵的上坐标(全部顶点)
for(int i = 0; i < G.vexnum; i++) {
printf(" %3d ", i);
}//for
printf("\n");
//输出左上角多余的空白
printf(" +");
//输出一条横线
for(int i = 0; i < G.vexnum; i++) {
printf("-----");
}//for
printf("\n");
for(int i = 0; i < G.vexnum; ++i) {
//输出邻接矩阵左边的坐标
printf(" %3d |", G.vexs[i]);
for(int j = 0; j < G.vexnum; ++j) {
if(D1[i][j] == INFINITY) {
printf(" ∞ ");
}//if
else {
printf(" %3d ", D1[i][j]);
}//else
}//for
printf("\n |\n");
}//for
//打印计算得到的最短路径
printf("\n->最短路径的计算结果:\n");
for(int i = 0; i < G.vexnum; ++i) {
for(int j = 0; j < G.vexnum; ++j) {
printf(" %d--->%d :%-6d ", G.vexs[i], G.vexs[j], D1[i][j]);
}//for
printf("\n");
}//for
//打印弗洛伊德算法用的P数组
printf("\n->弗洛伊德算法用的最短路径数组P[i][j][k]如下(T表示TRUE,F表示FALSE):\n");
for(int i = 0; i < G.vexnum; ++i) {
for(int j = 0; j < G.vexnum; ++j) {
for(int k = 0; k < G.vexnum; ++k){
printf("P[%d][%d][%d] = %c ", i, j, k, P1[i][j][k] == 1 ? 'T' : 'F');
}//for
printf("\n");
}//for
}//for
}//if
//测试销毁
printf("\n->测试销毁图: ");
DestroyGraph(G);
printf("成功!\n");
printf("演示结束,程序退出!\n");
return 0;
}
测试的有向网使用书上P188页的图7.34,可以直接去该页寻找最短路径的正确结果,以便于验证算法正确与否。
测试过程中程序的输入和输出:
-------------基于邻接矩阵的最短路径算法测试程序--------------
->测试图的创建:(最短路径算法要求图必须是有向网,请选择1)
请输入您想构造的图的类型:有向图输入0,有向网输入1,无向图输入2,无向网输入3):1
请依次输入有向网G的顶点数,弧数,用逗号隔开
6,8
请依次输入有向网G的顶点名称,用空格隔开
0 1 2 3 4 5
请依次输入有向网G每条弧依附的两顶点名称及权值,输完一组按回车
0 5 100
0 4 30
0 2 10
1 2 5
2 3 50
4 3 20
4 5 60
3 5 10
->创建成功后图的邻接矩阵:
0 1 2 3 4 5
+------------------------------
0 | ∞ ∞ 10 ∞ 30 100
|
1 | ∞ ∞ 5 ∞ ∞ ∞
|
2 | ∞ ∞ ∞ 50 ∞ ∞
|
3 | ∞ ∞ ∞ ∞ ∞ 10
|
4 | ∞ ∞ ∞ 20 ∞ 60
|
5 | ∞ ∞ ∞ ∞ ∞ ∞
|
->测试求有向网的最短路径:
->使用迪杰斯特拉算法求单源最短路径:
->迪杰斯特拉算法用到的最短路径数组P[i][j]如下(T表示TRUE,F表示FALSE):
F F F F F F
F F F F F F
T F T F F F
T F F T T F
T F F F T F
T F F T T T
顶点0到其他顶点的最短路径如下:
0 ---> 0 : 0
0 ---> 1 :65535
0 ---> 2 : 10
0 ---> 3 : 50
0 ---> 4 : 30
0 ---> 5 : 60
->使用弗洛伊德算法求任意两顶点最短路径:
->执行算法前的邻接矩阵如下:
0 1 2 3 4 5
+------------------------------
0 | 0 ∞ 10 ∞ 30 100
|
1 | ∞ 0 5 ∞ ∞ ∞
|
2 | ∞ ∞ 0 50 ∞ ∞
|
3 | ∞ ∞ ∞ 0 ∞ 10
|
4 | ∞ ∞ ∞ 20 0 60
|
5 | ∞ ∞ ∞ ∞ ∞ 0
|
->弗洛伊德算法用的D数组:
0 1 2 3 4 5
+------------------------------
0 | 0 ∞ 10 50 30 60
|
1 | ∞ 0 5 55 ∞ 65
|
2 | ∞ ∞ 0 50 ∞ 60
|
3 | ∞ ∞ ∞ 0 ∞ 10
|
4 | ∞ ∞ ∞ 20 0 30
|
5 | ∞ ∞ ∞ ∞ ∞ 0
|
->最短路径的计算结果:
0--->0 :0 0--->1 :65535 0--->2 :10 0--->3 :50 0--->4 :30 0--->5 :60
1--->0 :65535 1--->1 :0 1--->2 :5 1--->3 :55 1--->4 :65535 1--->5 :65
2--->0 :65535 2--->1 :65535 2--->2 :0 2--->3 :50 2--->4 :65535 2--->5 :60
3--->0 :65535 3--->1 :65535 3--->2 :65535 3--->3 :0 3--->4 :65535 3--->5 :10
4--->0 :65535 4--->1 :65535 4--->2 :65535 4--->3 :20 4--->4 :0 4--->5 :30
5--->0 :65535 5--->1 :65535 5--->2 :65535 5--->3 :65535 5--->4 :65535 5--->5 :0
->弗洛伊德算法用的最短路径数组P[i][j][k]如下(T表示TRUE,F表示FALSE):
P[0][0][0] = T P[0][0][1] = F P[0][0][2] = F P[0][0][3] = F P[0][0][4] = F P[0][0][5] = F
P[0][1][0] = F P[0][1][1] = F P[0][1][2] = F P[0][1][3] = F P[0][1][4] = F P[0][1][5] = F
P[0][2][0] = T P[0][2][1] = F P[0][2][2] = T P[0][2][3] = F P[0][2][4] = F P[0][2][5] = F
P[0][3][0] = T P[0][3][1] = F P[0][3][2] = F P[0][3][3] = T P[0][3][4] = T P[0][3][5] = F
P[0][4][0] = T P[0][4][1] = F P[0][4][2] = F P[0][4][3] = F P[0][4][4] = T P[0][4][5] = F
P[0][5][0] = T P[0][5][1] = F P[0][5][2] = F P[0][5][3] = T P[0][5][4] = T P[0][5][5] = T
P[1][0][0] = F P[1][0][1] = F P[1][0][2] = F P[1][0][3] = F P[1][0][4] = F P[1][0][5] = F
P[1][1][0] = F P[1][1][1] = T P[1][1][2] = F P[1][1][3] = F P[1][1][4] = F P[1][1][5] = F
P[1][2][0] = F P[1][2][1] = T P[1][2][2] = T P[1][2][3] = F P[1][2][4] = F P[1][2][5] = F
P[1][3][0] = F P[1][3][1] = T P[1][3][2] = T P[1][3][3] = T P[1][3][4] = F P[1][3][5] = F
P[1][4][0] = F P[1][4][1] = F P[1][4][2] = F P[1][4][3] = F P[1][4][4] = F P[1][4][5] = F
P[1][5][0] = F P[1][5][1] = T P[1][5][2] = T P[1][5][3] = T P[1][5][4] = F P[1][5][5] = T
P[2][0][0] = F P[2][0][1] = F P[2][0][2] = F P[2][0][3] = F P[2][0][4] = F P[2][0][5] = F
P[2][1][0] = F P[2][1][1] = F P[2][1][2] = F P[2][1][3] = F P[2][1][4] = F P[2][1][5] = F
P[2][2][0] = F P[2][2][1] = F P[2][2][2] = T P[2][2][3] = F P[2][2][4] = F P[2][2][5] = F
P[2][3][0] = F P[2][3][1] = F P[2][3][2] = T P[2][3][3] = T P[2][3][4] = F P[2][3][5] = F
P[2][4][0] = F P[2][4][1] = F P[2][4][2] = F P[2][4][3] = F P[2][4][4] = F P[2][4][5] = F
P[2][5][0] = F P[2][5][1] = F P[2][5][2] = T P[2][5][3] = T P[2][5][4] = F P[2][5][5] = T
P[3][0][0] = F P[3][0][1] = F P[3][0][2] = F P[3][0][3] = F P[3][0][4] = F P[3][0][5] = F
P[3][1][0] = F P[3][1][1] = F P[3][1][2] = F P[3][1][3] = F P[3][1][4] = F P[3][1][5] = F
P[3][2][0] = F P[3][2][1] = F P[3][2][2] = F P[3][2][3] = F P[3][2][4] = F P[3][2][5] = F
P[3][3][0] = F P[3][3][1] = F P[3][3][2] = F P[3][3][3] = T P[3][3][4] = F P[3][3][5] = F
P[3][4][0] = F P[3][4][1] = F P[3][4][2] = F P[3][4][3] = F P[3][4][4] = F P[3][4][5] = F
P[3][5][0] = F P[3][5][1] = F P[3][5][2] = F P[3][5][3] = T P[3][5][4] = F P[3][5][5] = T
P[4][0][0] = F P[4][0][1] = F P[4][0][2] = F P[4][0][3] = F P[4][0][4] = F P[4][0][5] = F
P[4][1][0] = F P[4][1][1] = F P[4][1][2] = F P[4][1][3] = F P[4][1][4] = F P[4][1][5] = F
P[4][2][0] = F P[4][2][1] = F P[4][2][2] = F P[4][2][3] = F P[4][2][4] = F P[4][2][5] = F
P[4][3][0] = F P[4][3][1] = F P[4][3][2] = F P[4][3][3] = T P[4][3][4] = T P[4][3][5] = F
P[4][4][0] = F P[4][4][1] = F P[4][4][2] = F P[4][4][3] = F P[4][4][4] = T P[4][4][5] = F
P[4][5][0] = F P[4][5][1] = F P[4][5][2] = F P[4][5][3] = T P[4][5][4] = T P[4][5][5] = T
P[5][0][0] = F P[5][0][1] = F P[5][0][2] = F P[5][0][3] = F P[5][0][4] = F P[5][0][5] = F
P[5][1][0] = F P[5][1][1] = F P[5][1][2] = F P[5][1][3] = F P[5][1][4] = F P[5][1][5] = F
P[5][2][0] = F P[5][2][1] = F P[5][2][2] = F P[5][2][3] = F P[5][2][4] = F P[5][2][5] = F
P[5][3][0] = F P[5][3][1] = F P[5][3][2] = F P[5][3][3] = F P[5][3][4] = F P[5][3][5] = F
P[5][4][0] = F P[5][4][1] = F P[5][4][2] = F P[5][4][3] = F P[5][4][4] = F P[5][4][5] = F
P[5][5][0] = F P[5][5][1] = F P[5][5][2] = F P[5][5][3] = F P[5][5][4] = F P[5][5][5] = T
->测试销毁图: 成功!
演示结束,程序退出!
--------------------------------
Process exited with return value 0
Press any key to continue . . .
总结:
迪杰斯特拉算法求得的是单源最短路径,想得到所有顶点之间的最短路径只能一遍遍的循环去求。
弗洛伊德算法可以求得各顶点之间的最短路径,且形式更加优雅。
下次文章会介绍拓扑排序算法的实现。感谢大家一直以来的支持和关注。再见!