数据结构编程笔记二十一:第七章 图 最短路径算法的实现

来源:互联网 发布:核盾网络验证使用方法 编辑:程序博客网 时间:2024/05/22 16:01

上次我们介绍了图的最小生成树算法的实现,这次介绍基于邻接矩阵的最短路径算法的实现。

还是老规矩:

程序在码云上可以下载。
地址: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 1000 4 300 2 101 2 52 3 504 3 204 5 603 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 :  00 ---> 1 :655350 ---> 2 : 100 ---> 3 : 500 ---> 4 : 300 ---> 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] = FP[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] = FP[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] = FP[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] = FP[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] = FP[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] = TP[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] = FP[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] = FP[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] = FP[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] = FP[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] = FP[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] = TP[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] = FP[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] = FP[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] = FP[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] = FP[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] = FP[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] = TP[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] = FP[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] = FP[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] = FP[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] = FP[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] = FP[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] = TP[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] = FP[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] = FP[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] = FP[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] = FP[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] = FP[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] = TP[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] = FP[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] = FP[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] = FP[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] = FP[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] = FP[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 0Press any key to continue . . .

总结:
迪杰斯特拉算法求得的是单源最短路径,想得到所有顶点之间的最短路径只能一遍遍的循环去求。
弗洛伊德算法可以求得各顶点之间的最短路径,且形式更加优雅。

下次文章会介绍拓扑排序算法的实现。感谢大家一直以来的支持和关注。再见!

阅读全文
0 0
原创粉丝点击