最短路径算法

权:与每条边(v,w)相联系的是穿越这条边的代价c_{v,w}

赋权路径长:一条路径v_{1}v_{2}...v_{N}的值为\sum_{i=1}^{N-1}c_{i,i+1},无权路径长就是边的条数N-1

单源最短路径问题:给定一个赋权图G=(V,E)和一个特定顶点s作为输入,找出从s到G中每个其他顶点的最短赋权路径

负值圈:若图中存在负值圈,那么会有问题,因为可以在负值圈中一直转,让路径无限小

性能:由于图是不可拆分的,因此不存在找从s到某一个顶点比从s到所有顶点快多于常数因子的算法,这也是图的问题无法应用任何并发算法和任何分布式算法的原因

一.无权最短路径

代码如下:

/* 使用队列改进的无权最短路径算法 */
void Unweighted(Table T)
{
    Queue Q;
    Vertex V, W;

    /* 创建队列 */
    Q = CreateQueue(NumVertex);
    MakeEmpty(Q);

    /* 将单发点S入队 */
    Enqueue(S, Q);

    while (!IsEmpty(Q))
    {
        V = Dequeue(Q);
        T[V].Known = True;

        /* 遍历V的邻接顶点,根据距离判断之前是否访问过,若没有,那么入队列 */
        for each W adjacent to V
            if (T[W].Dist == Infinity)
            {
                T[W].Dist = T[V].Dist + 1;
                T[W].Path = V;
                Enqueue(W, Q);
            }
    }

    DisposeQueue(Q);
}

二.赋权最短路径

Dijkstra算法:是贪婪算法,分阶段进行,每个阶段选择一个顶点v,它具有所有未知顶点中最小的d_{v},这里d_{v}是使用已知顶点作为中间顶点,从s到v的最短路径长。

正确性:每个阶段选择的顶点v一定是最优秀的,原因是,设通过一个未知顶点t到达v的路径比当前的d_{v}短,那么t就是当前阶段最优秀的,而不是v。

代码如下:

typedef int Vertex;
struct TableEntry
{
    List Header;
    int Known;      //标记顶点是否已知
    DistType Dist;  //通过已知顶点从s到当前顶点的距离
    Vertex Path;
};

#define NotAVertex (-1)
typedef struct TableEntry Table[NumVertex];

/* 初始化表,Start是所求的源点 */
void InitTable(Vertex Start, Graph G, Table T)
{
    int i;
    /* 将图读入table中 */
    ReadGraph(G, T);
    for (i = 0; i < NumVertex; i++)
    {
        T[i].Known = False;
        T[i].Dist = Infinity;
        T[i].Path = NotAVertex;
    }
    T[Start].dist = 0;
}

/* 打印路径,是递归 */
void PrintPath(Vertex V, Table T)
{
    if (T[V].Path != NotAVertex)
    {
        PrintPath(T[V].Path, T);
        printf("to");
    }
    printf("%v", V);
}

void Dijkstra(Table T)
{
    Vertex V, W;

    for (;;)
    {
        /* 从未知顶点中选择一个路径最短的 */
        V = smallest unknown distance vertex;
        /* 没有这样的顶点,说明所有顶点都遍历到了,结束 */
        if (V == NotAVertex)
            break;
        /* 标记为已知 */
        T[V].Known = True;
        /* 遍历V的邻接顶点,更新距离信息 */
        for each W adjacent to V
            if (!T[W].Known)
                /* Cvw是从v到w的距离 */
                if (T[V].Dist + Cvw < T[W].Dist)
                {
                    Decrease(T[W].Dist to T[V].Dist + Cvw);
                    T[W].Path = V;
                }
    }
}

时间分析:

若通过扫描表查找d_{v},那么每次都要花费O(V)时间,一共O(V^2)时间,更新d_{w}是常数时间,一共O(E)时间,加起来就是O(V^2+\left | E \right |)时间,若图是稠密的,那么就是边的线性时间

若图是稀疏的,那么不适合使用遍历的方式查找最小距离,应该将顶点存放在优先队列中,查找未知顶点中最小距离和标记为已知是一个deletemin的操作,更新T[W]是一个decreasekey操作,一共O(V)次deletemin和O(E)次decreasekey,加起来时间是O(\left | E \right |log\left | V \right |+\left | V \right |log\left | V \right |)。问题是使用何种优先队列,若使用二叉堆,由于对find操作支持不好,需要保存每个顶点在优先队列中的位置并更新时保存新的位置,有两种解决方案,一种是使用配对堆,一种是每次将新的d_{w}插入到二叉堆中,这样会增加深度到log\left | E \right |,由于|E|=\Theta (|V|^2),因此不改变时间界

三.具有负边值的图

若图中有负值边,那么dijkstra算法的正确性无法保证,因为当前阶段得到一个最优值,但是后面的未知顶点会产生更短的路径,此时需要改进dijkstra算法让它有回溯功能,消除已知顶点的概念,每次若某个顶点距离被更新了,那么需要重新处理这个顶点,代码如下:

/* 有负值边的图,时间O(EV) */
void WeightedNegative(Table T)
{
    Queue Q;
    Vertex V, W;

    Q = CreateQueue(NumVertex);
    MakeEmpty(Q);
    Enqueue(S, Q);

    while (!IsEmpty(Q))
    {
        /* 每个顶点可以出队V次 */
        V = Dequeue(Q);
        for each W adjacent to V
            if (T[V].Dist + Cvw < T[W].Dist)
            {
                T[W].Dist = T[V].Dist + Cvw;
                T[W].Path = V;
                /* 这个可以用位图实现 */
                if (W is not already in Q)
                    Enqueue(W, Q);
            }
    }
    DisposeQueue(Q);
}

若有负值圈,会出现死循环的情况,可以通过判断每个顶点出队的次数跳出循环,当任何一个顶点出队V+1次,说明有负值圈

四.无圈图

若事先知道了图是无圈的,那么可以应用拓扑排序的思想选择下一个顶点,因为从S到V只有一条路径可走,时间和拓扑排序一样都是O(|E|+|V|),代码如下:

/* 无圈图,使用拓扑排序的思想,时间是O(E + V) */
void Acyclic(Table T)
{
    Queue Q;
    Vertex V, W;

    Q = CreateQueue(NumVertex);
    Q = MakeEmpty();

    Enqueue(S, Q);

    while (!IsEmpty(Q))
    {
        V = Dequeue(Q);
        for each W adjacent to V
            if (--Indegree[W] == 0)
            {
                Enqueue(W, Q);
                T[W].Path = V;
                T[W].Dist = T[V].Dist + Cvw;
            }
    }
    Dispose(Q);
}

五.所有点对最短路径

由于已经有了单发点最短路径,对稀疏图可以运行|V|次单源最短路径算法,对稠密图可以使用动态规划,时间是O(|V|^3)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值