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

本文介绍基于邻接矩阵的最短路径算法实现,包括迪杰斯特拉算法和弗洛伊德算法。通过示例展示了如何求解单源最短路径和所有顶点间的最短路径。

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

还是老规矩:

程序在码云上可以下载。
地址: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 . . .

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值