最短路径

介绍:

在网图和非网图中,最短路径的含义是不同的。由于非网图它没有边上的权值,所谓的最短路径,其实就是指两顶点之间经过的边数最少的路径;而对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点是源点,最后一个顶点是终点。显然,我们研究网图更有实际意义,就地图来说,距离就是两顶点间的权值之和。而非网图完全可以理解为所有的边的权值都为1的网。

迪杰斯特拉(Dijkstra)算法

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

/* Dijkstra算法,求有向网G的v0顶点到其余顶点v最短路径P[v]及带权长度D[v] */
/* P[v]的值为前驱顶点下标,D[v]表示v0到v的最短路径长度和。 */
void ShortestPath_Dijkstra(MGraph G, int v0, Pathmatirx *P, ShortPathTable *D)
{
    int v, w, k, min;
    int final[MAXVEX];  /* final[w]=1表示求得顶点v0至vw的最短路径 */

    for (v=0; v<G.numVertexes; v++)     /* 初始化数据 */
    {
        final[v] = 0;           /* 全部顶点初始化为未知最短路径状态 */
        (*D)[v] = G.matirx[v0][v];  /* 将与v0点有连线的顶点加上权值 */
        (*P)[v] = 0;            /* 初始化路径数组P为0 */
    }

    (*D)[v0] = 0;               /* v0至v0路径为0 */
    final[v0] = 1;              /* v0至v0不需要求路径 */

    /* 开始主循环,每次求得v0到某个v顶点的最短路径 */
    for (v=1; v<G.numVertexes; v++)
    {
        min = INFINITY;         /* 当前所知离v0顶点的最近距离 */
        for (w=0; w<G.numVertexes; w++) /* 寻找离v0最近的顶点 */
        {
            if (!final[w] && (*D)[w]<min)
            {
                k = w;
                min = (*D)[w];  /* w顶点离v0顶点更近 */
            }
        }

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

        for (w=0; w<G.numVertexes; w++) /* 修正当前最短路径及距离 */
        {
            /* 如果经过v顶点的路径比现在这条路径的长度短的话 */
            if (!final[w] &&(min+G.matirx[k][w]<(*D)[w]))
            {
                /* 说明找到了更短的路径,修改D[w]和P[w] */
                (*D)[w] = min + G.matirx[k][w]; /* 修改当前路径长度 */
                (*P)[w] = k;
            }
        }
    }
}
  • 算法用邻接矩阵来存储图的相关数据,final数组用来标记哪些点已经加入最短路径中,D数组用来储存更新某个顶点到其它顶点的路径长度,算法每循环一次就找到一个顶点加入最短路径中,然后判断此顶点是否能缩短原先的距离,即经过一个中间结点后更新原先的路径长度,经过二个中间结点更新路径长度,经过三个、四个……直到最后所有顶点都在最短路径中,算法结束。
  • P数组用来储存最短路径的路线,即它们是如何连接的,源点要到达终点这条路怎么走,先到哪个点再到哪个点。
  • 此算法解决了从某个源点到其余各顶点的最短路径问题。时间复杂度为O(n2)。
  • 如果我们需要知道任一顶点到其余所有顶点的最短路径怎么办呢?简单的办法就是对每个顶点当作源点运行一次Dijkstra算法,等于在原有算法的基础上,再来一次循环,此时整个算法的时间复杂度为O(n3)。

弗洛伊德(Floyd)算法

typedef int Pathmatirx[MAXVEX][MAXVEX];
typedef int ShortPathTable[MAXVEX][MAXVEX];

/* Floyd算法,求网图G中各顶点v到其余顶点w最短路径P[v][w]及带权长度D[v][w] */
void ShortestPath_Floyd(MGraph G, Pathmatirx *P, ShortPathTable *D)
{
    int v, w, k;
    for (v=0; v<G.numVertexes; ++v)         /* 初始化D与P */
    {
        for (w=0; w<G.numVertexes; ++w)
        {
            (*D)[v][w] = G.matirx[v][w];    /* D[v][w]值即为对应点间的权值 */
            (*P)[v][w] = w;                 /* 初始化P */
        }
    }

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

    /* 最短路径的显示代码 */
    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存储的是顶点到顶点之间的权值,数组P存储的是顶点到顶点之间的路径。
  • k代表中转顶点的下标,v代表起始顶点,w代表结束顶点。
  • 循环每个顶点作为中间顶点,判断是否比原来的路径长度短,是则更新路径长度。
  • 它的代码简洁到就是一个二重循环初始化加一个三重循环权值修正,就完成了所有顶点到所有顶点的最短路径计算。如此简单的实现,真是巧妙之极。
  • 它的时间复杂度为O(n3)。
  • 如果你面临需要求所有顶点至所有顶点的最短路径问题时,弗洛伊德(Floyd)算法是不错的选择。

总结

我们虽然对求最短路径的两个算法举例都是无向图,但它们对有向图依然有效,因为二者的差异仅仅是邻接矩阵是否对称而已。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

I'm Mark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值