数据结构与算法——图的最短路径

574 阅读6分钟

1.迪杰斯特拉算法

1.1算法分析

迪杰斯特拉算法计算的是有向网中的某个顶点到其余所有顶点的最短路径。 以下图为例:

要确定源点V0到V8的最短路径,可以按如下步骤进行:

  1. 用V0出发,可以直接到达V1和V2,可以得到如下表格:

    从表格中可以看到,V0 到 V1 的距离最近,所以迪杰斯特拉算法设定 V0-V1 为 V0 到 V1 之间的最短路径,最短路径的权值和为1。

  2. 已经判断 V0-V1 是最短路径,所以以 V1 为起始点,判断 V1 到除了 V0 以外的其余各点之间的距离,如果对应的权值比前一张表格中记录的数值小,就说明网中有一条更短的路径,直接更新表格;反之表格中的数据不变。可以得到下面这个表格:

  3. 更新以后发现,V0-V2的路径最短,所以再以 V2 为起点,判断 V2 到除了 V0,V1 以外的其余各点之间的距离,如果对应的权值比前一张表格中记录的数值小,就说明网中有一条更短的路径,直接更新表格;反之表格中的数据不变。

  4. 再以V4为起点,更新表格如下:

  5. 再以V3为起点:

  6. 再以V6为起点:

  7. 再以V6=7为起点,得到最终的最小路径的权值16:

1.2代码结构

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

1.3代码实现

/*求得网图中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 点有连线的顶点最短路径值;
        (*D)[v] = G.arc[v0][v];
        //初始化路径数组p = 0;
        (*P)[v] = 0;
    }
    
    //V0到V0的路径为0
    (*D)[v0] = 0;
    //V0到V0 是没有路径的.
    final[v0] = 1;
    //v0到V0是没有路径的
    (*P)[v0] = -1;
    
  
    
    //2. 开始主循环,每次求得V0到某个顶点的最短路径
    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.把刚刚找到v0到v1最短路径的基础上,对于v1 与 其他顶点的边进行计算,得到v0与它们的当前最短距离;*/
        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;
            }
        }
    }
}

结果打印:

 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]);

2.弗洛伊德算法

2.1算法分析

弗洛伊德的核心思想是:对于网中的任意两个顶点(例如顶点 A 到顶点 B)来说,之间的最短路径不外乎有 2 种情况:

  1. 直接从顶点 A 到顶点 B 的弧的权值为顶点 A 到顶点 B 的最短路径;
  2. 从顶点 A 开始,经过若干个顶点,最终达到顶点 B,期间经过的弧的权值和为顶点 A 到顶点 B 的最短路径。

所以,弗洛伊德算法的核心为: 对于从顶点 A 到顶点 B 的最短路径,拿出网中所有的顶点进行如下判断:
Dis(A,K)+ Dis(K,B)< Dis(A,B)
其中,K 表示网中所有的顶点;Dis(A,B) 表示顶点 A 到顶点 B 的距离。

也就是说,拿出所有的顶点 K,判断经过顶点 K 是否存在一条可行路径比直达的路径的权值小,如果式子成立,说明确实存在一条权值更小的路径,此时只需要更新记录的权值和即可。

任意的两个顶点全部做以上的判断,最终遍历完成后记录的最终的权值即为对应顶点之间的最短路径。

2.2执行过程

初始化两个二维数组D和P,D用来存储顶点之间最短路径的权值,P用来存储最短路径。

  1. 当 K = 0时,也就是所有顶点都经过V0 中转, 计算是否有最短路径的变化。但是k=0时,并没有发⽣任何变换。
  2. 当 K = 1 时, 也就是所有顶点都经过V1 中转;更新数组D和P为
    通过V1的中转,V0与V2,V3,V4 之间的建立了连接,更新路径权值为4,8,6。V2与V4之间的最短路径权值,由于Dis(V2,V4)<Dis(V2,V1)+Dis(V1,V4),所以就没有更新。
    通过V1的中转,V0与V2,V3,V4 之间的建立了连接,更新P[0][2],P[0][3],P[0][4]的值为1。 3.如此循环遍历,知道遍历到V8,各个顶点之间的最短路径.

2.3代码实现

算法实现:

/*
 Floyd算法,求网图G中各顶点v到其余顶点w的最短路径P[v][w]及带权长度D[v][w]。
 Patharc 和 ShortPathTable 都是二维数组;
 */
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];
                }
            }
        }
    }
}

结果打印如下:

//打印所有可能的顶点之间的最短路径以及路线值
    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");
    }