如何计算图的最短路径?

算法导论(MIT 6.006 第15讲 第16讲 第17讲)

最短路径的定义是什么?

最短路径即拥有最小权重的路径p;
路径定义: p=< v0 v 0 , v0 v 0 ,…, vk v k >, 其中当 0i<k 0 ≤ i < k 时,有 ( vi v i , vi+1 v i + 1 ) E;
路径的权重:w(p)= Σk1i=0w(vi,vi+1) Σ i = 0 k − 1 w ( v i , v i + 1 ) ;

加上权重的数学表示方式

  1. 边存在权重的图:G(V,E,W) ,W是一个函数,作用于边,生成一个实数,即W(E)->R
  2. 顶点到自身的路径:( V0 V 0 )表示从( V0 V 0 )到( V0 V 0 )的路径,权重是0
  3. 两个顶点之间的最短路径:
    δ(u,v)={min{w(p)}u,vu,v δ ( u , v ) = { ∞ u , v 之 前 不 存 在 路 径 m i n { w ( p ) } u , v 之 间 存 在 路 径

E与V的关系 E=O( V2 V 2 )。对于有向图来讲,假设有两个顶点,v1,v2,他们之间只有4种连接情况,依次类推

为什么会有负的权重?

比如社交网络上的喜欢可以看做是正的权重,比喜欢可以看做是负的权重

负权重的边带来什么问题?

如果存在一个带有负权重的边,那么每经过一个循环,会减少原有的权重值,这样造成的现象是可以得到任何可以得到的权重值。比如路径p=

最短路径算法的一般思路是什么?

d(v) 表示从源点s到当前节点v的路径权重 , π[v] π [ v ] 表示当前最好的路径上,v的前一个节点 ,通过这种方式就能重构整个最短路径

针对没有负权重的环
1. 初始化 d[v] = , π[u] π [ u ] =NIL,d[s]=0
2. 通过某种方式选择边(u,v),执行Relax操作,去更新源点到选择的顶点的当前路径值,以及选择顶点的前一个节点

Relax(u,v,w):
    select edge(u,v):
        if d[v]>d[u]+w(u,v):
            d[v]=d[u]+w(u,v)
            PI[v]=u
    until all edges have d[v] <= d[u]+w(u,v)

relax操作的过程中会不会产生一个一个比 δ δ (s,v)还要小的值?

通过归纳法,假设有 d[u] δ δ (s,u)。已知的是 δ(s,v) δ ( s , v ) 表示s到v的最短路径,那么任意一个到v的顶点u和源点s到u的最短路径必定大于等于 δ(s,v) δ ( s , v ) ,也就是

δ(s,v)δ(s,u)+w(u,v) δ ( s , v ) ≤ δ ( s , u ) + w ( u , v )

通过前面的假设,则必定有 δ(s,v)d[u]+w(u,v)=d[v] δ ( s , v ) ≤ d [ u ] + w ( u , v ) = d [ v ] 。这说明,中间的过程的任意一个阶段产生的结果d[v]都不会比 δ δ (s,v)还要小

最短路径算法的一般思路问题一:错误的选边导致复杂度为指数级别

构造如下结构的图

边的权值按照 2n/2 2 n / 2 方式分配,图中给出的6个点的示例,如果全部显示的边( v0 v 0 , v2 v 2 )的权值为 2n/2 2 n / 2 ,并依次递减到1

这里写图片描述
假设源点为 v0 v 0 ,初始化选择的路径如下,可以得到从源点到各个点的路径长度。

这里写图片描述

此时,Relax( v4 v 4 , v6 v 6 )的边,会更新 v0 v 0 v6 v 6 的路径长度为13

这里写图片描述
再Relax( v2 v 2 , v4 v 4 )的边,会更新 v0 v 0 v4 v 4 的路径长度为10

这里写图片描述
由于新 v0 v 0 v4 v 4 的路径长度变短,那么( v0 v 0 , v5 v 5 )的路径会变短为11

这里写图片描述
这个时候有可能先选的执行Relax的边是 ( v5 v 5 , v6 v 6 ),那么( v0 v 0 , v6 v 6 )的路径会变短为12

这里写图片描述
再次Relax边( v4 v 4 , v6 v 6 ),那么( v0 v 0 , v6 v 6 )的路径会变短为11

这里写图片描述
针对有n个顶点的情况:
- 首先Relax( vn2 v n − 2 , vn v n ),使得d[ vn v n ]减1
- 再Relax( vn4 v n − 4 , vn2 v n − 2 ),使得d[ vn2 v n − 2 ]减2
- 然后Relax( vn2 v n − 2 , vn1 v n − 1 )和( vn1 v n − 1 , vn v n )使得d[ vn v n ]减1
- 再执行Relax( vn2 v n − 2 , vn v n ),使得d[ vn v n ]减1

可发现,当Relax的边( vn2 v n − 2 , vn v n )权重为1的时候,使得顶点d( vn v n )减1;当Relax边( vn4 v n − 4 , vn2 v n − 2 )权重为2的时候,使得顶点d( vn v n )减2,也就是从权重按照 1,2,4,…, 2(n/2)1 2 ( n / 2 ) − 1 , 2(n/2) 2 ( n / 2 ) 的方式执行的过程中,d( vn v n )需要执行减少的总次数为1+2+4+…+ 2(n/2) 2 ( n / 2 ) = 2(n/2)1 2 ( n / 2 ) − 1 ,也就是说,会执行的次数为指数级别

最短路径算法的一般思路问题二:负权重环

如果在源点到目标节点经过的路径上,经过环会导致权重减少,这个算法不会结束

如何获取有向无环图(DAG)中,单个源点到某个点的最短路径?

DAG表示只是没有环,可以存在负边权重
1. 对DAG进行拓扑排序,这样保证了u到v的路径一定是u在v之前
2. 找到源点,按照从左到右,DAG排列的顺序,对经过的每个顶点进行Relax操作,便得到了源点到所有顶点的最短路径

假设排序好的拓扑图如下,对于初始化时,每个源点到每个节点的距离都认为是

这里写图片描述
第一步从源点往下走,找到它的所有的边,对边执行Relax操作

这里写图片描述
源点执行完毕,然后按照拓扑排列的顺序,从左往右执行,由于Relax只更改小于的情况,因此只有最后两个节点的路径值被更新
这里写图片描述
继续往右执行Relax
这里写图片描述

继续往右执行Relax

这里写图片描述
至此执行完毕,可以看到源点到所有节点的最短路径,从左到右分别是 ,0,2,6,5,3

如果图中有环,但是经过这个环不会导致权重减少,如何计算最短路径?

使用Dijkstra算法。伪代码算法如下:

Dijkstra(G,w,s): //G是图,w是权值,s是源点
    Initialize(G,s) // 初始化,设置d[s]=0,其它都是无穷,以及PI
    S <- {}    //已知最短路径的点的集合
    Q <- V[G]  //需要被处理的顶点,可以看做是一个最小优先级队列,根据d()值进行排序
    while Q is not empty: //只要还有没处理的节点
        u <- Extract-Min(Q) //从节点中找出一个最小的路径权重的节点,并从Q中移除
        S <- S U {u} //将找到的节点并到S中
        for each vertex v belong to Adj
            Relax(u,v,w) //对边的d()值进行更新

例子如下,选择A为源点

这里写图片描述
1. 进行初始化,从A到其它节点的距离都是 ,即 S={} ,Q={A(0),B( ),C( ),D( ),E( )};
2. 获取队列中的最小值,此时是A本身,此时S={A(0)},然后进行一次Relax操作,即发现A能达到的顶点为B,C,更新后队列中的值为 Q={B(10),C(3),D( ),E( )};
3. 获取队列中的最小值,此时是C,S={A(0),C(3)},对选择的C做Relax,C能到达的节点为B,D,E,相应队列更新为:Q={B(7),D(11),E(5)};
4. 获取队列的最小值,此时是E,S={A(0),C(3),E(5)},对选择的E做Relax,E能到的节点为D,由于比现有的D值要大,所以没有更新,Q={B(7),D(11)};
5. 获取队列的最小值,此时是B,此时S={A(0),C(3),E(5),B(7)},B能到达的只剩下D了,B到D得到的值为9,要小,更新Q={D(9)}
6. 获取队列最小的值,此时是D,此时S={{A(0),C(3),E(5),B(7),D(9)},至此结束。

括号中的值表示路径距离

Dijkstra算法的时间复杂度

所有的耗时操作包括:
- 将所有的顶点插入优先级队列中,耗时为 θ(V) θ ( V ) ;
- 从优先级队列中提取一个最小的值,耗时为 θ(V) θ ( V ) ;
- Relax操作对边进行d值减少,耗时为 θ(E) θ ( E ) ;
实现优先级队列方式不同,耗时不同
1. 使用Array。 提取最小值花销: θ(V) θ ( V ) ,Relax对d值进行减少 θ(1) θ ( 1 ) ,操作所有的队列中的元素,那么时间就是 θ(VV+E1) θ ( V ∗ V + E ∗ 1 ) = θ(V2) θ ( V 2 )
2. 使用最小堆。提取最小值花销: θ(lgV) θ ( lg ⁡ V ) ,减少key的花销 θ(lgV) θ ( lg ⁡ V ) ,操作所有的队列中的元素,那么时间就是 θ(VlgV+ElgV) θ ( V ∗ lg ⁡ V + E ∗ lg ⁡ V )
3. 使用Fibonacci堆,提取最小值花销: θ(lgV) θ ( lg ⁡ V ) ,减少key的花销 θ(1) θ ( 1 ) ,能到达 θ(VlgV+E) θ ( V ∗ lg ⁡ V + E )

最直观的使用Dijkstra的感受是:以下图为例:这里写图片描述
假设绿色的点是源点,如果用这样长度的绳子将各个节点连接起来,那么拎起绿色的球,从上往下悬挂,那些蹦直的线相加就是源点到各个点的最短距离,比如绿色是源点,到其它点的最短距离分别是 7,12,18,22(颜色依次是紫色、蓝色、黄色、红色)

为什么Dijkstra不能处理负权重环的问题?

Dikstra不会去看已经处理好的节点,只会处理没有看到的节点,如果已经处理的节点都是最小的值,再不存在负权重环的情况下,是不会出现使得路径变小的情况。详见:https://stackoverflow.com/questions/6799172/negative-weights-using-dijkstras-algorithm/6799344#6799344

如果在源点到目标节点经过的路径上,有经过环且会导致权重减少,怎么处理最短路径问题?

使用Bellman-Ford算法。

Bellman-Ford(G,w,s):
    Initialuze(G,s)
    for i=1 to |V|-1:
        for each edge(u,v) belong to E:
            Relax(u,v,w)
    for each edge (u,v) belong to E:
        if d[v]>d[u]+w(u,v)
            report negative cycle exist

Bellman-Ford最终提供的是,如果没有负权重的环,那么能返回最短路径(d[v]= δ(s,v) δ ( s , v ) ),否则只是检测出存在负权重的环

耗时分析

两个for循环,分别为V,E,所以时间复杂度就是O(VE)

为什么Bellman-Ford算法在不存在负权重环的情况下能够计算最小路径?

只需要证明,如果不存在负权重的环,那么经过Bellman-Ford有d[v]= δ(s,v) δ ( s , v )

取一条拥有最少边的最短路径p=< v0 v 0 , v1 v 1 ,…, vk v k >,其中 v0 v 0 为s, vk v k =v。 如果不存在负权重的环,那么说明p是一条简单路径,这表明,k |V|-1。

这里也不可能是一个正环,即每经过这个环,权重增加,如果是那么它就不是最短路径了

当进行第一次循环的时候,取到的边( v0 v 0 , v1 v 1 )进行了Relax,那么有 \delta(s, v1 v 1 )=d[ v1 v 1 ]
进行第二次循环,取到的边( v1 v 1 , v2 v 2 )进行了Relax,那么有\delta(s, v2 v 2 )=d[ v2 v 2 ]
那么经过k轮循环之后,有\delta(s, vk v k )=d[ vk v k ],也就是说经过了|V|-1轮循环之后,每个从源点可达的顶点都计算了最短路径

简单路径(simple path):指除了起点和终点之外,其它顶点不会重复。对于简单路径p=< v0 v 0 , v1 v 1 ,…, vk v k >来讲,如果k>=|V|,那么路径上总的顶点数是|V|+1,但实际只有 |V|个顶点,那么必定存在一条重复的边,使得非起点终点重复了,也就是说他不是简单路径了

为什么Bellman-Ford算法能检测负权重环?

经过|V|-1轮循环之后,如果还有一条边能够Relax,那么当前从s到v的最短路径并不是简单路径,因为所有的节点都已经看过了,这时候肯定存在了重复的节点,也就是说存在一个负权重的环

如果对一个路径上有环,且所有权重值都是负权重,那么使用Bellman-Ford算法能得到最长路径吗?

不能,因为Bellman-Ford对于存在负权重的环的时候只会抛出异常,并没有计算路径,这实际是一个N-P的问题,即花的时间在指数级别或者之上

类似的,如果要求不经过负权重的环的情况下,计算最短路径,也并不是件容易的事情

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值