12--The shortest path of the application of the graph

For the basic knowledge of graphs and the implementation of graph storage, as well as other related knowledge of data structures and algorithms, please refer to the article "Data Structures and Algorithms Basic Knowledge Article Summary" .

The shortest path of a graph

image.png

图的最短路径It refers to the smallest path of the edges that the connection passes through among the connection paths between 连通图any one.两个顶点权值和

This article explains two methods for finding the shortest path of a graph: Dijkstra算法and Floyd算法.

2. Dijkstra's algorithm

1. Algorithm ideas

Step 1: From the V0starting point, there are those connected to V0 V1和V2, and the weights of the edges are respectively 1和5, because 1比5小, so choose V1and record the sum of the weights of the edges passed at this time as sum = 1, and mark V0 and V1 as having joined the shortest path

image.png

Step 2: V1There are connected vertices V2、V3、V4, and the weight of the edge 3、5、7is 最小3, so select the vertex V2, sum + 3 = 4, and record that V2 has been added to the shortest path

image.png

Step 3: V2The connected vertex is V4、V5, and the weight of the edge 1和7is 最小1, so select the vertex V4, sum+1 = 5, and record that V4 has been added to the shortest path

image.png

Step 4: V4Connect to the vertices V3、V5、V6、V7that are not added to the shortest path, the weight of the edge 2、3、6、9is 最小2, so choose V3, sum + 2 = 7, record that V3 has joined the shortest path

image.png

Step 5: V3The vertices that are connected to and have not joined the shortest path are V6, the weight of the edge is 3, and can only be selected V6, sum + 3 = 10, record that V6 has joined the shortest path

image.png

第六步:与V6相连且没有加入最短路径的顶点为V7、V8,边的权值为4、7最小为4,所以选择V7,sum + 2 = 12,记录V7已经加入了最短路径

image.png 第七步:与V7相连的且没有加入最短路径的顶点为V5、V8,边的权值为5、4最小为4,所以选择V4,sum + 4 = 16,记录V8已经加入了最短路径。

image.png 经过上面的步骤,我们最终得到了从V0到V8最短路径权值和为:

  • 最短路径:V0 -> V1 -> V2 -> V4 -> V3 -> V6 -> V7 -> V8;
  • 权值和:1 + 3 + 1 + 2 + 3 + 2 + 4 = 16。

虽然我们最终得到了最终的正确结果,但是上面的分析过程还是存在着一些漏洞的,比如:

  • 1.如果V7与V5的权值小于V7与V8的权值呢?
  • 2.如果V6与V8的权值小于V6与V7的权值+V7与V8的权值呢?
  • 3.你的思考是什么?

其实前面的分析只是大概讲述了Dijkstra算法的思路,不是真实的过程,之所以讲这个思路是为了让读者对Dijkstra的算法思路有一个初步的认识。下面我们进一步讲解Dijkstra算法的实现逻辑。

2.代码逻辑

1.首先使用邻接矩阵的顺序存储将图存储在内存中

image.png

2.设计3个数组来实现算法思路中的求解步骤,在求解过程中会更新这3个数组

image.png

  • 1.final数组:表示V0到顶点Vw是否已经求得了最短路径的标记,如果已经求得结果,则标记final[w] = 1;final数组的初始化所有元素都为0。
  • 2.D数组:V0到顶点Vw所经过的所有边的权值和最小值,在求解的过程中,这个最小值会不断的更新,遇到比当前所存的值更小的值就更新。D数组的初始值为V0在邻接矩阵中对应的边表数组,因为刚开始时,只能通过边表数组来确定与V0相连的顶点的权值。

image.png

  • 3.P数组:P数组的索引值表示图中所有顶点的下标值,P数组中的值表示索引对应的顶点的前驱顶点的下标,P数组初始值全为0,表示所有顶点的前驱都为0.

image.png image.png

如上图,V0没有前驱,所以P[0] = -1,V1的前驱是V0,所以P[1] = 0,V2、V3、V4的前驱是V1,所以P[2]、P[3]、P[3] = 1,虽然此时三个顶点的前驱都为1,但是在最短路径中一个顶点只能是另外唯一一个顶点的前驱,所以P数组会在算法求解的过程为进行更新,请认准最终结果。

3.代码实现

1.定义一些状态值和数据类型

#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 int Patharc[MAXVEX];

//用于存储到各点最短路径权值的和
typedef int ShortPathTable[MAXVEX];
复制代码

2.邻接矩阵顺序存储的数据结构设计

typedef struct
{
    int vexs[MAXVEX];//顶点数组

    int arc[MAXVEX][MAXVEX];//邻接矩阵:边表数组

    int numVertexes, numEdges;//顶点数、边数

}MGraph;
复制代码

3.邻接矩阵的顺序存储实现

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];
        }
    }
}
复制代码

4.Dijkstra算法代码实现

/*10.2 求得网图中2点间最短路径

 Dijkstra 算法

 G: 网图;

 v0: V0开始的顶点;

 p[v]: 前驱顶点下标;

 D[v]: 表示从V0到V的最短路径长度和;

 */
void ShortestPath_Dijkstra(MGraph G, int v0, Patharc *P, ShortPathTable *D)
{
    int v,w,k,min;
    k = 0;

    //final[w] = 1 表示已经求得顶点V0~Vw的最短路径
    int final[MAXVEX];

    //1.初始化数据
    for(v=0; v<G.numVertexes; v++)
    {
        //全部顶点初始化为未知最短路径状态0
        final[v] = 0;

        //将V0的边表数组(与V0有连接的顶点的权值)存入D数组
        (*D)[v] = G.arc[v0][v];

        //初始化路径数组p = 0
        (*P)[v] = 0;
    }

    //V0到V0的路径为0
    (*D)[v0] = 0;

    //记录V0到V0已经求过最短路径了
    final[v0] = 1;

    //v0没有前驱顶点,记录为-1
    (*P)[v0] = -1;

    //2. 开始主循环,有numVertexes个顶点,所以遍历numVertexes-1次
    for(v = 1; v < G.numVertexes; v++)
    {
        //与V0有连接的顶点的边中的最小权值
        min=INFINITYC;

        //3.寻找与V0有连接的顶点中权值最小的边和对应的顶点
        for(w=0; w<G.numVertexes; w++)
        {
            if(!final[w] && (*D)[w]< min)
            {
                k=w;
                //w顶点距离V0顶点更近
                min = (*D)[w];
            }
        }

        //将目前找到最近的顶点标记为1;
        final[k] = 1;

        //4.遍历与k有连接的所有顶点,如果它们的权值小于D数组中原有对应索引的权值,则更新,并记录w的前驱结点是k
        for(w=0; w<G.numVertexes; w++)
        {
            //如果经过v顶点的路径比现在这条路径长度短,则更新
            if(!final[w] && (min + G.arc[k][w]<(*D)[w]))
            {
                //找到更短路径, 则修改D[W],P[W]
                //修改当前路径的长度
                (*D)[w] = min + G.arc[k][w];
                (*P)[w] = k;//w的前驱是k
            }
        }
    }
}
复制代码

5.调试代码

int main(void)
{
    printf("最短路径-Dijkstra算法\n");
    int i,j,v0;

    MGraph G;
    Patharc P;
    ShortPathTable D;
    v0 = 0;

    CreateMGraph(&G);
    ShortestPath_Dijkstra(G, v0, &P, &D);

    printf("最短路径路线:\n");

    for(i=1;i<G.numVertexes;++i)
    {
        printf("v%d -> v%d : ",v0,i);
        j = i;

        while(P[j] != -1)
        {
            printf("%d ",P[j]);
            j = P[j];
        }
        printf("\n");
    }

    printf("\n最短路径权值和\n");

    for(i=1;i<G.numVertexes;++i)
        printf("v%d -> v%d : %d \n",G.vexs[0],G.vexs[i],D[i]);

    printf("\n");
    return 0;
}
复制代码

执行结果: image.png

通过Dijkstra算法,最终求得V0任一顶点之间的最短路径。这个算法的局限性在于,你需要在邻接矩阵存储数据时,把指定某个顶点作为第0个内存的数据,而且只能求指定顶点任意顶点之间的最短路径。后面介绍的Floyd算法就能求解出任意两个顶点之间的最短路径

4.执行过程

第一次执行

代码执行时数据的变化 image.png 图中最短路径的变化 image.png

第二次执行

代码执行时数据的变化

image.png

图中最短路径的变化

image.png

第三次执行

代码执行时数据的变化 image.png

图的最短路径的变化

image.png

第四次执行

代码执行时数据的变化

image.png

图的最短路径的变化

image.png

第五次执行

代码执行时数据的变化

image.png

图的最短路径的变化

image.png

第六次执行

代码执行时数据的变化

image.png

图的最短路径的变化

image.png

第七次执行

代码执行时数据的变化

image.png

图的最短路径的变化

image.png

第八次执行

代码执行时数据的变化 image.png 图的最短路径的变化

image.png

最终得到的D数组和P数组的数据为:

image.png

5.小结

  • 1.Dijkstra算法只能求解指定的顶点到其他任意顶点的最短路径和对应的权值;
  • 2.Dijkstra算法所求的指定的顶点的数据必须存储在邻接矩阵的第0个内存的位置。

三、Floyd算法

1.算法思路

image.png 同样还是这个图,现在通过邻接矩阵的顺序存储到内存中

image.png

  • 1.首先我们知道,连通图中的任意顶点都可以通过有限条边与图中的其他任何一个顶点相连;
  • 2.邻接矩阵描述是的任意两个顶点之间的直接连接关系,顶点自己与自己相连时,边的权值为0顶点与顶点有连接时,边的权值是一个大于0的非无穷大的数值;当顶点与顶点之间没有直接连接时,边的权值为无穷大
  • 3.有了1和2两个条件,那么对于图中的任意两个顶点都可以通过另外的任意一个顶点或多个相连的顶点连接;当两个顶点有直接连接时,它们的边的权值有可能大于通过另外一个顶点相连的顶点连接的边的权值和。假如我们可以通过另外的一个或多个顶点来到目标顶点,并且所经历的边的权值和比直接到对应顶点的边的权值更小,那么我们就可以选择更优的路径了,举例如下:

image.png

假设现在只有上图中V0、V1、V2三个顶点,它在邻接矩阵中的存储如左上图;由图的可知,直接从V1到V2的边的权值为5,在邻接矩阵中的体现是D[1][2] = 5;而从V1到V0再到V2所经历的权值和为2+1 = 3,3比5小,于是当需要求解V1到V2的最短路径时,我们就可以选择从V1->V0->V2了。

  • 4.在3思路的基础上,对于上面完整的图的最短路径的求解,我们就可以通过图中的任意一个顶点作为中间顶点,分别求解图中的任意两个顶点之间的边的权值和,如果比之前方案的权值和更小,就可以更新这两个顶点之间边的权值了;
  • 5.当遇到遇到无穷大时,说明两个顶点之间不用通过另外的顶点相连,此时我们无需更新两个顶点的边的权值。

通过上面的分析,我们需要确定的是,如果比较两个顶点之间的权值和是否小于通过另外一个或多个顶点相连的权值和,这里提供如下公式

image.png

顶点v和顶点w之间的权值和,取顶点v和顶点w的权值,与顶点v和顶点O的权值 + 顶点O和顶点w的权值的较小值

2.代码实现思路

image.png

  • 1.设计二维数组D,用来记录顶点之间的权值,它的初始值设置为图的邻接矩阵的数据;在算法实现的过种中,会不断的更新,最终得到的结果即为任意两个顶点之间的最短路径的权值和;
  • 2.设计一个二维数组P,用来记录任意两个顶点之间最短路径的顶点下标;它的初始值每一列都是对应的列值,表示列对应的顶点与其他顶点之间都是通过列对应的顶点相连的,即没有相连。

3.代码实现

1.图的存储

Dijkstra算法以中已经介绍了邻接矩阵顺序存储的实现

2.D和P数组的定义

typedef int Patharc[MAXVEX][MAXVEX];

typedef int ShortPathTable[MAXVEX][MAXVEX];
复制代码

3.Floyd算法的实现

void ShortestPath_Floyd(MGraph G, Patharc *P, ShortPathTable *D)
{
    int v,w,k;

    //1.初始化D与P 矩阵
    for(v=0; v<G.numVertexes; ++v)
    {
        for(w=0; w<G.numVertexes; ++w)
        {
            //D[v][w]值即为邻接矩阵的边表数组的值
            (*D)[v][w]=G.arc[v][w];

             //初始化P P[v][w] = w
            (*P)[v][w]=w;
        }
    }

    //2.k表示经过的中转顶点
    for(k=0; k<G.numVertexes; ++k)
    {
        for(v=0; v<G.numVertexes; ++v)
        {
            for(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];
                }
            }
        }
    }
}
复制代码

4.调式代码

int main(void)
{
    printf("Hello,最短路径弗洛伊德Floyd算法");

    int v,w,k;

    MGraph G;

    Patharc P;

    ShortPathTable D; //求某点到其余各点的最短路径

    CreateMGraph(&G);

    ShortestPath_Floyd(G,&P,&D);

    //打印所有可能的顶点之间的最短路径以及路线值
    printf("各顶点间最短路径如下:\n");

    for(v=0; v<G.numVertexes; ++v)
    {
        for(w=v+1; w<G.numVertexes; 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.numVertexes; ++v)
    {
        for(w=0; w<G.numVertexes; ++w)
        {
            printf("%d\t",D[v][w]);
        }
        printf("\n");
    }

    //打印最终变换后的最短路径P数组
    printf("最短路径P数组\n");

    for(v=0; v<G.numVertexes; ++v)
    {
        for(w=0; w<G.numVertexes; ++w)
        {
            printf("%d ",P[v][w]);
        }
        printf("\n");
    }
    return 0;
}
复制代码

5.执行结果

image.png

Through the above algorithm, the final values ​​of D array and P array are:

image.png

4. Summary

Floyd算法Solved for 任意two vertices 最短路径.

4. Summary

图的最短路径The summation algorithm Dijkstra算法of Floyd算法. The understanding of the two algorithms is very difficult, and readers can experience it a lot.

Guess you like

Origin juejin.im/post/7082324814865629215
Recommended