数据结构-图进阶-最短路径(Dijkstra&&Floyd)

283 阅读9分钟

前言

本文讲述如何求得图中的最短路径,主要讲解两个算法,Dijkstra算法和Floyd算法

举个例子

下图中从V0出发到达V8,求出权值和最小的的路径

最终结果

Dijkstra算法

辅助数组

使用三个数组来辅助计算过程中的值,

  • final数组:标示从源点(V0)到任意一个顶点是否已经求得了最短路径。已经求得用1标示,没有求得用0
  • D数组:标示源点(V0)可以到达的某个顶点的路径。下标是顶点,值是源点到达某顶点的路径权值和。
  • P数组:到达当前顶点的路径前驱顶点下标。

代码

准备代码:类型与图的创建
#define MAXVERTEX 10
#define INVALID_VALUE 65535

//P数组类型
typedef int ArcPath[MAXVERTEX];
//D数组类型
typedef int ShortPathTable[MAXVERTEX];
//图结构体
typedef struct Graph {
    int vex[MAXVERTEX];
    int arc[MAXVERTEX][MAXVERTEX];
    int numVex;
    int numEdge;
}MGraph;
//创建图
void CreateGraph(MGraph *G) {
    G->numVex = 9;
    G->numEdge = 16;
    
    for (int i = 0; i < G->numVex; i++) {
        G->vex[i] = i;
        for (int j = 0; j < G->numVex; j++) {
            G->arc[i][j] = (i == j)? 0: INVALID_VALUE;
        }
    }
    
    G->arc[0][1] = 1;
    G->arc[0][2] = 5;
    
    G->arc[1][2] = 3;
    G->arc[1][3] = 7;
    G->arc[1][4] = 5;
    
    G->arc[2][4] = 1;
    G->arc[2][5] = 7;
    
    G->arc[3][4] = 2;
    G->arc[3][6] = 3;
    
    G->arc[4][5] = 3;
    G->arc[4][6] = 6;
    G->arc[4][7] = 9;
    
    G->arc[5][7] = 5;
    
    G->arc[6][7] = 2;
    G->arc[6][8] = 7;
    
    G->arc[7][8] = 4;
    
    for (int i = 0; i < G->numVex; i++) {
        for (int j = 0; j < G->numVex; j++) {
            G->arc[j][i] = G->arc[i][j];
            printf("%d ", G->arc[i][j]);
        }
        printf("\n");
    }
}
算法
//Dijkstra算法,参数:图、开始点、P数组、D数组
void ShortestPath_Dijkstra(MGraph g, int start, ArcPath *P, ShortPathTable *D) {
    //定义final数组
    int final[MAXVERTEX] = {0};
    //初始化开始点
    int k = start;
    //初始化三个数组数据
    final[k] = 1;//final中标记开始点已经加入
    (*P)[k] = -1;//开始点的前驱结点为-1
    //将开始点的邻接矩阵中的值赋值给D,也就是将与开始点有关联的顶点记录到D中
    for (int i = 0; i < g.numVex; i++) {
        (*D)[i] = g.arc[k][i];
    }
    
    for (int i = 0; i < g.numVex; i++) {
        //获取D数组中的最小路径的位置m和权值min
        int min = INVALID_VALUE;
        int m = 0;
        for (int j = 0; j < g.numVex; j++) {
            if (final[j] == 0 && (*D)[j] < min) {
                m = j;
                min = (*D)[j];
            }
        }
        
        //更新final数组
        final[m] = 1;
        
        //将找到的最小路径的位置对应的邻接矩阵中的顶点信息,加入到D数组中,并更新P数组
        for (int k = 0; k < g.numVex; k++) {
            //判断:没有记录在final数组中 && D中的值大于邻接矩阵中的值,更新D数组:获取更显的路径权值,更新P数组记录前驱顶点的位置
            if (final[k] == 0 && g.arc[m][k] + min < (*D)[k]) {
                (*D)[k] = g.arc[m][k] + min;
                (*P)[k] = m;
            }
        }
    }
    
    printf("final:");
    for (int i = 0; i < g.numVex; i++) {
        printf("%d ", final[i]);
    }
    printf("\n");
    
    printf("D:");
    for (int i = 0; i < g.numVex; i++) {
        printf("%d ", (*D)[i]);
    }
    printf("\n");
    
    printf("P:");
    for (int i = 0; i < g.numVex; i++) {
        printf("%d ", (*P)[i]);
    }
    printf("\n");
    
}
运行
int main(int argc, const char * argv[]) {
    printf("Hello, Dijkstra!\n");
    
    MGraph g;
    CreateGraph(&g);
    
    ArcPath P = {0};
    ShortPathTable D = {INVALID_VALUE};
    int start = 0;
    
    ShortestPath_Dijkstra(g, start, &P, &D);
    
    printf("最短路径路线:\n");
    int j;
    for(int i=1;i<g.numVex;++i)
    {
        printf("v%d -> v%d : ",start,i);
        j=i;
        while(P[j]!=-1)
        {
            printf("%d ",P[j]);
            j=P[j];
        }
        printf("\n");
    }
    
    printf("\n最短路径权值和\n");
    for(int i=1;i<g.numVex;++i)
        printf("v%d -> v%d : %d \n",g.vex[0],g.vex[i],D[i]);
    
    printf("\n");
    
    return 0;
}

通过结果的展示,Dijkstra算法是把源点到终点的所经历的点的路径都计算出来了,这也是此算法和Floyd算法的区别。接下来我们来讲解Floyd算法。

Floyd算法

与Dijkstra的区别

上面学到的Dijkstra算法,是以源点为出发点,算出了到达终点路径中经过的所有顶点的路径值。而Floyd算法更加全面,将任意两个点的路径都会记录算出。所以说Floyd算法,需要二维数组的辅助。

二维数组-辅助

需要两个二位数辅助

  • D数组:初始获取邻接矩阵中的所有内容
  • P数组:同样也是个二维数组,记录前驱结点

简单示例以及公式

用一个简单的图结构解释一下Floyd算法是如何操作的

邻接矩阵
V0 V1 V2
V0 0 2 1
V1 2 0 5
V2 1 5 0

初始化D数组,将邻接矩阵赋值给D数组。

  • 计算V1-V2的路径,通过D数组中的记录D[1][2]=5
  • 计算V1-V0-V2的路径,通过D数组中的记录D[1][0]=2,D[0][2]=1,两个路径和2+1=3
  • V1-V2路径 大于 V1-V0-V2路径,更新D数组,参考表格中的2+1=3
V0 V1 V2
V0 0 2 1
V1 2 0 2+1=3
V2 1 2+1=3 0

同理P二维数组也就行相关位置修改,如图

公式

代码

准备代码:类型与图的创建
//图结构体
typedef struct Graph{
    int vex[MAXVERTEX];
    int arc[MAXVERTEX][MAXVERTEX];
    int numVex;
    int numEdge;
}MGraph;

//P数组类型
typedef int ArcPath[MAXVERTEX][MAXVERTEX];
//D数组类型
typedef int ShortPathTable[MAXVERTEX][MAXVERTEX];

void CreateGraph(MGraph *G) {
    G->numVex = 9;
    G->numEdge = 16;
    
    for (int i = 0; i < G->numVex; i++) {
        G->vex[i] = i;
        for (int j = 0; j < G->numVex; j++) {
            G->arc[i][j] = (i == j)? 0: INVALID_VALUE;
        }
    }
    
    G->arc[0][1] = 1;
    G->arc[0][2] = 5;
    
    G->arc[1][2] = 3;
    G->arc[1][3] = 7;
    G->arc[1][4] = 5;
    
    G->arc[2][4] = 1;
    G->arc[2][5] = 7;
    
    G->arc[3][4] = 2;
    G->arc[3][6] = 3;
    
    G->arc[4][5] = 3;
    G->arc[4][6] = 6;
    G->arc[4][7] = 9;
    
    G->arc[5][7] = 5;
    
    G->arc[6][7] = 2;
    G->arc[6][8] = 7;
    
    G->arc[7][8] = 4;
    
    for (int i = 0; i < G->numVex; i++) {
        for (int j = 0; j < G->numVex; j++) {
            G->arc[j][i] = G->arc[i][j];
            printf("%d ", G->arc[i][j]);
        }
        printf("\n");
    }
}

算法
//算法,参数:图,P二维数组,D二维数组
void ShortestPath_Floyd(MGraph g, ArcPath *P, ShortPathTable *D) {
    //初始化两个二维数组
    for (int i = 0; i < g.numVex; i++) {
        for (int j = 0; j < g.numVex; j++) {
            (*D)[i][j] = g.arc[i][j];
            (*P)[i][j] = j;
        }
    }
    
    
    for (int i = 0; i < g.numVex; i++) {
        for (int j = 0; j < g.numVex; j++) {
            for (int k = 0; k < g.numVex; k++) {
                //公式,更新D和P
                if ((*D)[j][k] > (*D)[j][i] + (*D)[i][k]) {
                    (*D)[j][k] = (*D)[j][i] + (*D)[i][k];
                    (*P)[j][k] = (*P)[j][i];
                }
            }
        }
    }
}
运行
int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, Floyd!\n");
    
    int v, w, k;
    
    MGraph g;
    CreateGraph(&g);
    
    ArcPath p;
    ShortPathTable d;
    
    ShortestPath_Floyd(g, &p, &d);
    
    //打印所有可能的顶点之间的最短路径以及路线值
    printf("各顶点间最短路径如下:\n");
    for(v=0; v<g.numVex; v++)
    {
        for(w=v+1; w<g.numVex; w++)
        {
            printf("v%d-v%d weight: %d ",v,w,d[v][w]);
            //获得第一个路径顶点下标
            k = p[v][w];
            //打印源点
            printf(" path: %d",v);
            //如果路径顶点下标不是终点
            while(k!=w)
            {
                //打印路径顶点
                printf(" -> %d",k);
                //获得下一个路径顶点下标
                k=p[k][w];
            }
            //打印终点
            printf(" -> %d\n",w);
        }
        printf("\n");
    }
    
    //打印最终变换后的最短路径D数组
    printf("最短路径D数组\n");
    for(v=0; v<g.numVex; v++)
    {
        for(w=0; w<g.numVex; w++)
        {
            printf("%d\t",d[v][w]);
        }
        printf("\n");
    }
    //打印最终变换后的最短路径P数组
    printf("最短路径P数组\n");
    for(v=0; v<g.numVex; v++)
    {
        for(w=0; w<g.numVex; w++)
        {
            printf("%d ",p[v][w]);
        }
        printf("\n");
    }
    
    return 0;
}
Hello, Floyd!
0 1 5 65535 65535 65535 65535 65535 65535 
1 0 3 7 5 65535 65535 65535 65535 
5 3 0 65535 1 7 65535 65535 65535 
65535 7 65535 0 2 65535 3 65535 65535 
65535 5 1 2 0 3 6 9 65535 
65535 65535 7 65535 3 0 65535 5 65535 
65535 65535 65535 3 6 65535 0 2 7 
65535 65535 65535 65535 9 5 2 0 4 
65535 65535 65535 65535 65535 65535 7 4 0 
各顶点间最短路径如下:
v0-v1 weight: 1  path: 0 -> 1
v0-v2 weight: 4  path: 0 -> 1 -> 2
v0-v3 weight: 7  path: 0 -> 1 -> 2 -> 4 -> 3
v0-v4 weight: 5  path: 0 -> 1 -> 2 -> 4
v0-v5 weight: 8  path: 0 -> 1 -> 2 -> 4 -> 5
v0-v6 weight: 10  path: 0 -> 1 -> 2 -> 4 -> 3 -> 6
v0-v7 weight: 12  path: 0 -> 1 -> 2 -> 4 -> 3 -> 6 -> 7
v0-v8 weight: 16  path: 0 -> 1 -> 2 -> 4 -> 3 -> 6 -> 7 -> 8

v1-v2 weight: 3  path: 1 -> 2
v1-v3 weight: 6  path: 1 -> 2 -> 4 -> 3
v1-v4 weight: 4  path: 1 -> 2 -> 4
v1-v5 weight: 7  path: 1 -> 2 -> 4 -> 5
v1-v6 weight: 9  path: 1 -> 2 -> 4 -> 3 -> 6
v1-v7 weight: 11  path: 1 -> 2 -> 4 -> 3 -> 6 -> 7
v1-v8 weight: 15  path: 1 -> 2 -> 4 -> 3 -> 6 -> 7 -> 8

v2-v3 weight: 3  path: 2 -> 4 -> 3
v2-v4 weight: 1  path: 2 -> 4
v2-v5 weight: 4  path: 2 -> 4 -> 5
v2-v6 weight: 6  path: 2 -> 4 -> 3 -> 6
v2-v7 weight: 8  path: 2 -> 4 -> 3 -> 6 -> 7
v2-v8 weight: 12  path: 2 -> 4 -> 3 -> 6 -> 7 -> 8

v3-v4 weight: 2  path: 3 -> 4
v3-v5 weight: 5  path: 3 -> 4 -> 5
v3-v6 weight: 3  path: 3 -> 6
v3-v7 weight: 5  path: 3 -> 6 -> 7
v3-v8 weight: 9  path: 3 -> 6 -> 7 -> 8

v4-v5 weight: 3  path: 4 -> 5
v4-v6 weight: 5  path: 4 -> 3 -> 6
v4-v7 weight: 7  path: 4 -> 3 -> 6 -> 7
v4-v8 weight: 11  path: 4 -> 3 -> 6 -> 7 -> 8

v5-v6 weight: 7  path: 5 -> 7 -> 6
v5-v7 weight: 5  path: 5 -> 7
v5-v8 weight: 9  path: 5 -> 7 -> 8

v6-v7 weight: 2  path: 6 -> 7
v6-v8 weight: 6  path: 6 -> 7 -> 8

v7-v8 weight: 4  path: 7 -> 8


最短路径D数组
0	1	4	7	5	8	10	12	16	
1	0	3	6	4	7	9	11	15	
4	3	0	3	1	4	6	8	12	
7	6	3	0	2	5	3	5	9	
5	4	1	2	0	3	5	7	11	
8	7	4	5	3	0	7	5	9	
10	9	6	3	5	7	0	2	6	
12	11	8	5	7	5	2	0	4	
16	15	12	9	11	9	6	4	0	
最短路径P数组
0 1 1 1 1 1 1 1 1 
0 1 2 2 2 2 2 2 2 
1 1 2 4 4 4 4 4 4 
4 4 4 3 4 4 6 6 6 
2 2 2 3 4 5 3 3 3 
4 4 4 4 4 5 7 7 7 
3 3 3 3 3 7 6 7 7 
6 6 6 6 6 5 6 7 8 
7 7 7 7 7 7 7 7 8 
Program ended with exit code: 0