算法基础篇-关于图的路径决策算法解析

895 阅读9分钟

算法系列篇章-可以参照如下顺序阅读

题干:求以下给定图的两个顶点之间的最短路径

图的邻接矩阵如下:

图的初始化

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

#define MAXEDGE 20
#define MAXVEX 20
#define INFINITYC 65535

typedef int Status;
typedef struct
{
    int vexs[MAXVEX];
    int arc[MAXVEX][MAXVEX];
    int numVertexes, numEdges;
}MGraph;

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

/*10.1 创建邻近矩阵*/
void CreateMGraph(MGraph *G)
{
    int i, j;
    
    G->numEdges=16;
    G->numVertexes=9;
    
    for (i = 0; i < G->numVertexes; i++)
    {
        G->vexs[i]=i;
    }
    
    for (i = 0; i < G->numVertexes; i++)
    {
        for ( j = 0; j < G->numVertexes; j++)
        {
            if (i==j)
                G->arc[i][j]=0;
            else
                G->arc[i][j] = G->arc[j][i] = INFINITYC;
        }
    }
    
    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(i = 0; i < G->numVertexes; i++)
    {
        for(j = i; j < G->numVertexes; j++)
        {
            G->arc[j][i] =G->arc[i][j];
        }
    }
}

一、最短路径-Dijkstra算法分析

1.思路分析

  • 新建3个数组final数组(记录0-n节点,是否已经找到最短路径,当前节点已经加入最短路径),D数组(表示V0 到某个顶点 Vw 的路路径),P数组(记录当前顶点的前置顶点)
  • 前置条件 V0V0的路径为0,即(*D)[v0] = 0;
  • 前置条件 V0V0是没有路径的,即final[v0] = 1;
  • 前置条件 V0V0是没有路路径的,所以(*P)[v0] = -1;
  • 临时变量k(记录当前顶点位置),min记录V0k顶点的最短路径值
  • 遍历k1也就是V1顶点位置开始,直到V8位置,针对每个k值,找到每个顶点从V0k顶点之前的最短路径,最短路径公式为!final[w] && min + G.arc[k][w] < (*D)[w]
  • 将最短路径更新到D数组,遍历结束之后,将k顶点加入final数组标示,更新P中每个顶点的前置节点

2.执行分析

2.1 初始状态如下,因为从V0顶点开始,k=0,我们通过初始化已经处理,所以从k=1开始

2.2 当k=1时,状态如下:

伪代码流程说明

// 最小路径值更新条件 !final[w] && min + G.arc[k][w] < (*D)[w]
w = 0, final[0] = 1 条件不不满⾜足;
w = 1, final[1] = 0 ,G.arc[1][1] = 0 ; 1 < 1 条件不不满⾜足;
w = 2, final[2] = 0 ,G.arc[1][2] = 3 ; 1+3 < D[2]=5 找到V0 -> V2 更更短路路径. 更更新D[2] = 4 ; p[2] = 1;
w = 3, final[3] = 0 , G.arc[1][3] = 7 ; 1+7 < D[3]=∞ 找到V0 -> V3 更更短路路径. 更更新D[3] = 1 + 7 = 8; p[3]=1;
w = 4, final[4] = 0 , G.arc[1][4] = 5 ; 1+5 < D[4]=∞ 找到V0 -> V4 更更短路路径. 更更新D[4] = 1 + 5 = 6; p[4]= 1;
w = 5, final[5] = 0 , G.arc[1][5] = ∞ ; 1+∞ < D[5]=∞ 条件不不成⽴立;
w = 6, final[6] = 0 , G.arc[1][6] = ∞ ; 1+∞ < D[6]=∞ 条件不不成⽴立;
w = 7, final[7] = 0 , G.arc[1][7] = ∞ ; 1+∞ < D[7]=∞ 条件不不成⽴立;
w = 8, final[8] = 0 , G.arc[1][8] = ∞ ; 1+∞ < D[8]=∞ 条件不不成⽴立

2.3 最终结果

  • D 数组中存储都是V0-Vw的最短路路径值;
  • P 数组描述的是V0 ~ Vw 的路路径;

3.代码实现

void ShortestPath_Dijkstra(MGraph G, int v0, Patharc *P, ShortPathTable *D){
    int v,w,k,min;
    k = 0; // 代表当前的节点
    // 1. 记录0-n节点,是否已经找到最短路径,当前节点已经加入最短路径
    int final[MAXVEX];
    // 2.初始化
    for(v = 0; v < G.numVertexes; v++)
    {
        // 所有节点都是未知状态
        final[v] = 0;
        (*D)[v] = G.arc[v0][v];
        (*P)[v] = 0;
    }
    // 3. 从v0开始的初始态赋值
    (*D)[v0] = 0;
    (*D)[v0] = 1;
    (*P)[v0] = -1;
    // 4. 主循环,求解v0->某个顶点的最短路径
    for(v=1; v<G.numVertexes; v++){
        min = INFINITYC; // 无穷大值
        for(w=0; w< G.numVertexes; w++){
            if(!final[w] && (*D)[w]<min)
            {
                k=w;
                //w顶点距离V0顶点更近
                min = (*D)[w];
            }
        }
        // 记录当前已经求解的节点
        final[k] = 1;
        
        // 5. 求解k节点的全部选择
        for(w=0; w<G.numVertexes; w++){
            if(!final[w] && min + G.arc[k][w] < (*D)[w])
            {
                // 更新最短路径
                (*D)[w] = min + G.arc[k][w];
                (*P)[w] = k;
            }
        }
    }
}

4.结果预期

二、最短路径-Floyd算法

Floyd算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似。 Floyd算法适用于APSP(All Pairs Shortest Paths,多源最短路径),是一种动态规划算法,稠密图效果最佳,边权可正可负。此算法简单有效,由于三重循环结构紧凑,对于稠密图,效率要高于执行|V|Dijkstra算法,也要高于执行|V|SPFA算法。

1.思路分析

1.1 路径矩阵

  • 通过一个图的权值矩阵求出它的每两点间的最短路径矩阵。
  • 从图的带权邻接矩阵G=[a(i,j)]n×n开始,递归地进行n次更新,即由矩阵D(0)=G,按一个公式,构造出矩阵D(1);又用同样地公式由D(1)构造出D(2);……;最后又用同样的公式由D(n-1)构造出矩阵D(n)。矩阵D(n)ij列元素便是i号顶点到j号顶点的最短路径长度,称D(n)为图的距离矩阵,同时还可引入一个后继节点矩阵P来记录两点间的最短路径。
  • 采用松弛技术(松弛操作),即求解两个顶点之前的最短路径,对在ij之间的所有其他点进行一次松弛。所以时间复杂度为O(n^3);

1.2 状态转移方程

其状态转移方程如下:

map[i,j]= min{map[i,k]+map[k,j],map[i,j]};
// map[i,j]表示i到j的最短距离,K是穷举i,j的断点

1.3 算法过程

把图用邻接矩阵G表示出来,如果从ViVj有路可达,则G[i][j]=dd表示该路的长度;否则G[i][j]=无穷大。定义一个矩阵D用来记录所插入点的信息,D[i][j]表示从ViVj需要经过的点,初始化D[i][j]=j。把各个顶点插入图中,比较插点后的距离与原来的距离,G[i][j] = min( G[i][j], G[i][k]+G[k][j] ),如果G[i][j]的值变小,则D[i][j]=k。在G中包含有两点之间最短道路的信息,而在D中则包含了最短通路径的信息。

  • 1,从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大
  • 2,对于每一对顶点 ij,看看是否存在一个顶点 w 使得从 iw 再到 j 比已知的路径更短。如果是更新它。

2.模拟分析

2.1 初始状态

  • 当k为0的时候,D数组用来保存顶点直接最短路径权值
  • P数组保存最短路径信息

2.2 k = 1的状态, 也就是所有顶点都经过V1中转

根据以下伪代码计算过程,当k=1,从v=0,开始遍历w=[0~8]的所有路径,更新最短路径情况,然后在从v=[0~8]做二重遍历,找到所有以V1为中转顶点的路径最小权值

// 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+∞; 所以不不更更新;

// k=1 V=2 W=[0~8] 算法执⾏行行过程分析
D[2][0] = 5, D[2][1] + D[1][0] = 3+1=4, 因为4<5,所以更更新D[2][0]=4;
D[2][1] = 3, D[2][1] + D[1][1] = 3+0=3, 所以不不更更新
D[2][2] = 0, D[2][1] + D[1][2] = 3 + 3 =6 , 所以不不更更新
D[2][3] = ∞, D[2][1] + D[1][3] = 3+7 = 10; 因为10<∞,所以更更新D[2][3] = 10; D[2][4] = 5, D[2][1] + D[1][4] = 3+5= 8;所以不不更更新
D[2][5] = 1, D[2][1] + D[1][5] =3+∞; 所以不不更更新;
D[2][6] = 7, D[2][1] + D[1][6] =3+∞; 所以不不更更新;
D[2][7] = ∞, D[2][1] + D[1][7] =3+∞; 所以不不更更新;
D[2][8] = ∞, D[2][1] + D[1][8] =3+∞; 所以不不更更新;
......

2.3 最终结果,将2.2过程,再次从k=[0~8]做三重遍历

可以看到 Dijkstra算法的D数组中存储都是V0-Vw的最短路路径值和Floyd算法的矩阵的第V0行的数值一致

2.4 求解最终结果

所有顶点到所有顶点之间的最短路径权值都已经计算完成; 那么我们如何从Floyd算法中 找到具体的最短路径了? 例如怎么求得V0~V8的最短路径以及路径总和?

V0 ~ V8为例, 从P数组中, P[0][8] = 1, 得到要经过顶点V1, 然后将1取代0得到P[1][8] = 2; 说明需要经过V2,则将2取代1得到P[2][8] = 4, 则表示需要经过V4,则将4取代2得到P[4][8] = 3, 说明...... 以此类推,推导最终的最短路路径为V0~V1~V2~V4~V3~V6~V7~V8;

3.代码实现

void ShortestPath_Floyd(MGraph G, Patharc *P, ShortPathTable *D){
    int v,w,k;
    //1.初始化
    for(v=0; v<G.num; v++){
        for(w=0; w<G.num; w++){
            (*D)[v][w] = G.arc[v][w];
            (*P)[v][w] = w;    
        }
    }
    // 2.k 标示要经过的中转节点,v行,w列
    for(k=0; v<G.num; k++){
        for(v=0; w<G.num; v++){
            for(w=0; v<G.num; w++){
                if((*D)[v][w] > (*D)[v][k] + (*D)[k][v])
                {
                    (*D)[v][w] = (*D)[v][k] + (*D)[k][v];
                    (*P)[v][w] = (*P)[v][k];
                }
            }
        }
    }
}

4.结果预期