数据结构与算法(十五) -- 图的最短路径

254 阅读5分钟

一、图的最短路径

在现实生活中, 我们经常会面临一个路径选择的问题, 例如: 乘坐地铁, 在整个地铁线路中以最快的方式达到自己需要到达的站点.

整个地铁图就可以看作是一个网图, 来求A站到B站效率最高的线路. 对于网图来说, 最短路径是指两个顶点之间经过的边上权值只和最少的路径. 并且称路径上第一个顶点为源点, 最后一个顶点为终点.

如图需要找到V0到V8到最小路径. 这里探索两个算法 -- 迪杰斯特拉(Djkstra)算法、弗洛伊德(Floyd)算法

二、迪杰斯特拉(Djkstra)算法

这是一个按路径长度递增的次序产生最短路径的算法.

  1. 从V0开始, 找最小权值短边, 就是连接顶点V1,
  2. 从V1开始, 找最小权值短边, 就是连接顶点V2
  3. 从V2开始, 找最小权值短边, 就是连接顶点V4

依此类推找到V8, 此时得到的是局部最优路径.

在这个算法里需要用到三个数组: final数组、D数组、P数组

  • final数组: 表示V0到某个顶点Vw是否已经求得里最短路径的标记. 如果V0到Vw已经有结果, 则finale[w] = 1
  • D数组: 表示V0到某个顶点Vw的路径
  • P数组: 当前顶点的前驱顶点的下标

例如:

  1. 当前顶点为V0, V0到V0没有路径即为0, 所以final[0] = 1
  2. 当前顶点为V0, V0到V1 V2分别为1 5, 所以D[1] = 1, D[2] = 5,
  3. 当前顶点为V0, V0没有前驱, 为P[0] = -1
  4. 比较D数组, 得到最小到路径为1, 即V0 V1为最小路径, 所以选择顶点V1

再次进行处理:

  1. 当前顶点为V1, V1到V2为3, 已知V0到V1为1, 所以V0到V2可以为4, D[2] = 5 > 4, 更新D[2] = 4
  2. V1到V3为7, 已知V0到V1为1, 所以V0到V3可以为8, 更新D[3] = 8
  3. V1到V4为5, 已知V0到V1为1, 所以V0到V4可以为6, 更新D[4] = 6
  4. 当前顶点为V1,V2 V3 V4上一个顶点为V1, 记录P[2] = 1, P[3] = 1, P[4] = 1

依次类推.

2.1、Djkstra算法代码实现

/*用于存储最短路径下标的数组*/
typedef int Patharc[MAXVEX];
/*用于存储到各点最短路径权值的和*/
typedef int ShortPathTable[MAXVEX];

void ShortestPath_Dijkstra(MGraph G, int v0, Patharc *P, ShortPathTable *D) {
    
    int final[MAXVEX];
    
    /*初始化数据*/
    for(int v = 0; v < G.numVertexes; v++)
    {
        //全部顶点初始化为未知最短路径状态0
        final[v] = 0;
        //将与V0 点有连线的顶点最短路径值;
        (*D)[v] = G.arc[v0][v];
        //初始化路径数组p = 0;
        (*P)[v] = 0;
    }
    final[v0] = 1;
    (*P)[v0] = -1;
    
    //找V0到某个顶点最短路径
    int k = 0;
    for (int i = 0; i < G.numVertexes; i++) {
        int min = INFINITYC;
        
        //找到当前最小的边
        for (int j = 0; j < G.numVertexes; j++) {
            if (!final[j] && (*D)[j] < min) {
                min = (*D)[j];
                k = j;
            }
        }
        final[k] = 1;
        for (int j = 0; j < G.numVertexes; j++) {
            //如果经过v顶点的路径比现在这条路径长度短,则更新
            if (!final[j] && (min + G.arc[k][j] < (*D)[j])) {
                //修改当前路径的长度
                (*D)[j] = min + G.arc[k][j];
                (*P)[j]=k;
            }
        }
    }
    
    //打印最短路径
    printf("最短路径路线:\n");
    int j = 0;
    for(int i = 1; i < G.numVertexes;++i)
    {
        printf("v%d -> v%d : V%d ", v0, i, i);
        j=i;
        while((*P)[j] != -1)
        {
            printf("V%d ", (*P)[j]);
            j = (*P)[j];
        }
        printf("\n");
    }
}

三、弗洛伊德(Floyd)算法

首先看一下例子:

定义两个二维数组D[3][3] P[3][3]. D代表顶点到顶点到最短路径权值和的矩阵. P代表对应顶点的最小路径的前驱矩阵.在处理前将D命名为 D^-1, P命名为P^-1.

由图可知, V1到V2距离为5, 但是V1->V0->V2距离为3, 所以V0->V2和V2->V0可以修改为3. 按照这样来对D^-1进行处理得到D^0

此时V2的前驱节点为V0, 所以修改P^-1的对应位置得到P^0

所以根绝这种规律就有来一个公式:

D^0[v][w] = min\{ D^-1[v][w], D^-1[v][k] + D^-1[k][w]\}

接下来其实就是在D^0 P^0的基础上继续处理所有顶点经过v1和v2后到达另一顶点的最短路径, 得到D^1 P^1 直到所有的顶点都处理完毕.

以此图为例子创建D^-1, P^-1

  1. 首先初始化得到两个二维数组 D^-1, P^-1
  2. 当k = 1, v = 0, w=[0~8], 根据公式:
    • D[0][0] = 0, D[0][1] + D[1][0] = 0, 不更新
    • D[0][1] = 1, D[0][1] + D[1][1] = 1, 不更新
    • D[0][2] = 5, D[0][1] + D[1][2] = 1 + 3 = 4, 4<5, 所以D[0][2] = 4
    • D[0][3] = ∞, D[0][1] + D[1][3] = 1 + 7 = 8, 8<∞, 所以D[0][3] = 8
    • D[0][4] = ∞, D[0][1] + D[1][4] = 1 + 5 = 6, 6<∞, 所以D[0][4] = 6
    • D[0][5] = ∞, D[0][1] + D[1][5] = 1 + ∞, 不更新
    • D[0][6] = ∞, D[0][1] + D[1][6] = 1 + ∞, 不更新
    • D[0][7] = ∞, D[0][1] + D[1][7] = 1 + ∞, 不更新
    • D[0][8] = ∞, D[0][1] + D[1][8] = 1 + ∞, 不更新
  3. 当k = 1, v = 1, w=[0~8], 根据公式:
    • D[1][0] = 0, D[1][1] + D[1][0] = 1, 不更新
    • D[1][1] = 1, D[1][1] + D[1][1] = 0, 不更新
    • D[1][2] = 5, D[1][1] + D[1][2] = 0 + 3 = 3, 不更新
    • D[1][3] = ∞, D[1][1] + D[1][3] = 0 + 7 = 7, 不更新
    • D[1][4] = ∞, D[1][1] + D[1][4] = 0 + 5 = 5, 不更新
    • D[1][5] = ∞, D[1][1] + D[1][5] = 0 + ∞, 不更新
    • D[1][6] = ∞, D[1][1] + D[1][6] = 0 + ∞, 不更新
    • D[1][7] = ∞, D[1][1] + D[1][7] = 0 + ∞, 不更新
    • D[1][8] = ∞, D[1][1] + D[1][8] = 0 + ∞, 不更新

依次进行得到最终的D^8数组为:

由图就可得知V0到V8最短距离为16.

路线为取P[0][8] = 1, P[1][8] = 2, P[2][8] = 4, P[4][8] = 3, P[3][8] = 6.....

得到最终路线为: V0 V1 V2 V4 V3 V6 V7 V8

3.1、Floyd算法代码实现

typedef int Patharc_Floyd[MAXVEX][MAXVEX];
typedef int ShortPathTable_Floyd[MAXVEX][MAXVEX];

void ShortestPath_Floyd(MGraph G, Patharc_Floyd *P, ShortPathTable_Floyd *D) {
    
    //初始化D P数组
    for (int v = 0; v < G.numVertexes; v++) {
        for (int w = 0; w < G.numVertexes; w++) {
            (*D)[v][w] = G.arc[v][w];
            (*P)[v][w] = w;
        }
    }
    
    for (int k = 0; k < G.numVertexes; k++) {
        for (int v = 0; v < G.numVertexes; v++) {
            for (int w = 0; w < G.numVertexes; w++) {
                //如果经过下标为k顶点路径比原两点间路径更短
                if ((*D)[v][w] > (*D)[v][k] + (*D)[k][w]) {
                    //将当前两点间权值设为更小的一个
                    (*D)[v][w]=(*D)[v][k]+(*D)[k][w];
                    //路径设置为经过下标为k的顶点
                    (*P)[v][w]=(*P)[v][k];
                }
            }
        }
    }
}