读《图算法,Robert Sedgewick》笔记 —— 最短路径

读《图算法,Robert Sedgewick》笔记 —— 最短路径

发表于:2008年4月1日 14时17分0秒阅读(1)评论(0) 本文链接:http://user.qzone.qq.com/40348105/blog/1207030620
   最短路径(Shortest Path)是在实际应用中非常有用的工具,将该问题细分,可以分为点到点最短路径(source-sink),单源点的最短路径(single-source),所有点到所有点(all-pairs)以及带负边情况下的最短路径。
  
    为了简化我们的问题,设置以下几个限制:
?     有向图,无向图可以看作每条边对应两条方向相反的有向边
?     无负环,可以想象,如果存在负环则路径值可不断减小,含负环的最短路径是NP的,暂不讨论。

单源点最短路径的简单算法
    单源点最短路径(无负边)可以用Dijkstra算法较好的解决,其思想类似与求最小生成树(MST)的Prim算法。只是Dijkstra将优先队列的权由两点的边改为了从源点到下一点的路径:
        Prim : Priority= edge.weight()                        // 从v点到w点的weight
        Dijkstra: Priority= wt[v] + edge.weight()            // 从源点到w点的值,wt[v]表示源点到v点的值
    注意,wt保存的就是Priority。
    类似于Prim,Dijkstra算法的复杂度主要取决于优先队列的实现,普通的Dijkstra算法能在线性时间内解决单源点无负边的最短路径问题,即复杂度为V2。采用了优先队列的数据结构后,可以在ElgdV - Elg2V之间解决问题(d表示采用d-堆)。
    点到点的无负边最短路径也可以用Dijkstra来解决,从源点出发,当搜索到目标点时停止搜索。十分容易理解,我就不罗嗦了。

All-Pairs 的最短路径问题
    正如大多数教材中所讲到的,求单源点无负边最短路径用Dijkstra,而求所有点最短路径用Floyd。确实,我们将用到Floyd算法,但是,并不是说所有情况下Floyd都是最佳选择。
    对于没有学过Floyd的人来说,在掌握了Dijkstra之后遇到All-Pairs最短路径问题的第一反应可能会是:计算所有点的单源点最短路径,不就可以得到所有点的最短路径了吗。简单得描述一下算法就是执行V次Dijkstra算法,自然其复杂度在最优情况下可以是VElgdV。
    Floyd可以说是Warshall算法的扩展了,三个for循环便可以解决一个复杂的问题,应该说是十分经典的。从它的三层循环可以看出,它的复杂度是V3,除了在第二层for中加点判断可以略微提高效率,几乎没有其他办法再减少它的复杂度。
    比较两种算法,不难得出以下的结论:对于稀疏的图,采用V次Dijkstra比较出色,对于茂密的图,可以使用Floyd算法。另外,Floyd可以处理带负边的图。

无环网络的最短路径问题
    无环网络(与DAG略微有区别)较带环网络而言,因为无环,所以边的正负是无所谓的。
记 得拓扑网络中有的点可能只做源点没有入度,那么在求最短路径中就搜索不到该点了,我们提出多源点的最短路径问题(Multisource shortest paths)了描述这种情况下求最短路径。其实我们可以将该问题转化为单源点的最短路径问题,只需加一个哑结点,其指向所有的无入度结点,来作为新的源, 而哑结点的所有边权为0。
    在一般图(带正环)中求最长路径问题,类似在带负环图中求最短路径问题,是NP难的。不过在无环网络中却可以求最长路径,因为无环网络无正环,所以可以确定最长的路径,而且最长路径对于DAG很有意义。所以我们先考虑在DAG中求最长路径(最短路径算法类似):
    利用DAG的强大武器——拓扑排序,可以避免Dijkstra中使用优先队列的损耗,因此效率高于Dijkstra。
    只要按拓扑排序后的序列遍历每个结点,刷新每条边,最终就可以得到最长路径了。
   求多源点的最长或最短路径问题可以在线性时间内解决,即复杂度E。
    对于所有点的最短路径,我们当然可以运行V次上一段的算法来求,这样复杂度是VE。除此以外,还存在着无环图中求所有点的最短路径,而且它可以避免多次使用拓扑排序。
类似于求DAG的传递闭包问题,采用DFS和DP方法,可以得到一个有效的算法。伪代码如下:
        储存边的二维数组 p;
        储存double的二维数组 d;
        递归的函数(Graph 图,int 遍历结点v){
            遍历从v结点出发的所有边:
                边e=该边;
                int t=边的另一端;
                double w=边的权;
                若d[s][t]>w ,更新d[s][t]=w,p[s][t]=e;
                若t未遍历,递归遍历t;
                遍历从t结点出发的所有边:
                    若d[s][i]>w+d[t][i]:
                        更新d[s][i]=w+d[t][i],p[s][i]=e;
        }
   该算法复杂度为VE(适用于负边)。

欧几里德网络(Euclidean Networks)的最短路径问题
    Euclidean Networks是指平面上有许多点,点与点之间的权值是这两点间的几何距离,这样的网络。
    一般,Euclidean Networks都比较大,即点比较多,在这里只考虑求点到点最短路径,由于距离无负值所以不必考虑负边的情况。那么参考前面介绍的方法,用Dijkstra是最好的选择了,用PFS(优先队列)保存待检查的结点然后选择最短的处理。
    由于Euclidean Networks的边是依据几何距离来决定的,所以我们采用几种特殊手段来加速算法,减小搜索范围。首先考虑PFS的优先权,Dijkstra中使用 w[v]+edge.weight()作为优先权,在Euclidean Networks中可以将待检查点到目标点的距离加入考虑范围,这是一种启发式搜索的思想。这样可以对Dijkstra做以下修改(s源点、d终点、 dist几何距离函数、v正在检查点、w待检查点):
1.     对wt[s]初始化为dist(s,d)
2.     优先权改为 ( wt[v] + edge.weight() + dist (w,d) - dist (v,d) ),即从s到w的路径加w到d的距离。
(熟悉启发式搜索的话,可以认为f'()= wt[v]-dist(v,d) , g'()=edge.weight()+dist(w,d), h'()=f'()+g'())
    这样修改的优点可以通过一个简单的例子来理解:假设目前正检查的结点v,待检查的有w1(离v很近,不在v到d连线上)和w2(在v到d的连线上)。如果 采用Dijkstra,按优先权先遍历w1,然后w2,最后d,求得的是v-w2-d。但如果修改优先级,则可以避免遍历w1,直接得到v-w2-d的结 果。如果在大规模的数据中采用该方法可以提快搜索找到目标解。
  
    但是,启发式搜索只是加快达到目标解的速度,而不会节省任何时间和空间。
    欧几里德启发式搜索作为A*算法的一种算法,当然需要一个界限函数来消除已不可能达到更优解的情况,也就是剪支。简单的方法是在找到一个可能解时,将优先权会大于可能解的待检查点全部忽略。
    欧几里德启发式搜索的限界可以理解是一个以s为原点的大圆中,有一个以s和d为焦点,形状受目前的解所影响的椭圆。Dijkstra会对整个圆进行搜索, 而启发式搜索只对该椭圆(因为焦点在圆内,而且解在不停更新变小,所以可以想象椭圆相对于大圆十分小)进行搜索。所以无论是空间还是时间,启发式搜索都大 大优于一般Dijkstra。
    实现欧几里德启发式函数的另一个方法是对边重新赋权。简单描述为:对每条边v-w更新,即加上dist(w,d) - dist(v,d) ,对于wt[s]的初始化仍然是dist(s,d)。然后运行标准的最短路径算法。对边重新赋权稍后还会用到。

其他问题
    首先定义一种有用的技术手段——Reduction(可能翻译为衰减吧)
    Definition    We say that a problem Areduces toanother problem B if we can use an algorithm that solves     B to develop an algorithm that solves A, in a total amount of time that is, in the worst case, no more than a     constant times the worst-case running time of the algorithm that solves B. We say that two problems are        equivalentif they reduce to each other.
    定义        如果可以从解决B问题的算法设计一种解决A问题的算法,且最坏情况下的时间总量不    超过B算法在    最坏情况下的运行时间的整数倍,则我们称A问题衰减为B问题。当两个问题可以    互相衰减时,称其等价。
    比如Floyd和Warshall是如此相似,我们可以说传递闭包问题reduces to所有点最短路径问题(反之不可)。因此,传递闭包可以用Floyd求,而且复杂度与Warshall是相同的。
    再如对于边无限制的网络中,求最长和最短边问题是等价的。
    Job Scheduling(我就不对相关问题名称做翻译了)的问题,对于某个作业需要先完成其他几个作业,每个作业有时间属性,要求在某些限制条件下完成所有作业的最短时间。首先考虑没有任何限制条件的简单调度。
    Difference constraints问题,对x0到xn一系列变量设置一些限制,每个限制指其中两个变量的差不小于给定常数。例如 x1-x0>=0.41         x7-x1=0.41     等等。
    Linear programming问题,Difference constraints问题的一般化。

   可以将Job-scheduling 问题reduces to Difference-constraints问题,当a工作必须在完成b工作后开始,那么可以有a-b>=Tb 的关系。
   Difference-constraints中常数为正数时的问题equivalent to 无环网络的单源点最长路径。所以对于一般无限制的Job-scheduling问题可以用无环网络的单源点最长路径解决。
    考虑限制了最后期限的Job-scheduling问题,前面已经说过Job-scheduling可以reduce到Difference- constraints,其实如果作业是带有期限的话就相当于Difference-constraints中有xi - xj<=dj,即 xj-xi>=-dj。也就是Difference-constraints中的常数可以为负。
   对于作业有期限的Job-scheduling问题可以reduces to 带负边无负环的最短路径问题。
    Reduce方法的另一个用途是判断某类问题是否是NP-Hard的问题,NP难的问题reduce to 另一个问题,则另一个问题是NP难的。
   带负边的网络求最短路径是NP难的。
摘自《图算法》的一些Reduction
    A    B    A=>B implication    example  
1    easy    easy    new B lower bound    sorting => EMST  
2    easy    tractable    none    TC=>APSP(+)  
3    easy    intractable    none    SSSP(DAG)=>SSSP(±)  
4    easy    unknown    none      
5    tractable    easy    A easy      
6    tractable    tractable    new A solution    DC(+)=>SSSP(DAG)  
7    tractable    intractable    none      
8    tractable    unknown    none      
9    intractable    easy    profound      
10    intractable    tractable    profound      
11    intractable    intractable    same as 1 or 6    SSLP()=>SSSP(±)  
12    intractable    unknown    B intractable    HP=>SSSP(±)  
13    unknown    easy    A easy    JS=>SSSP(DAG)  
14    unknown    tractable    A tractable      
15    unknown    intractable    A solvable      
16    unknown    unknown    same as 1 or 6    JSWD=>SSSP(±)  
Key:
    EMST    Euclidean minimum spanning tree
    TC        transitive closure
    APSP    all-pairs shortest paths
    SSSP    single-source shortest paths
    SSLP    single-source longest paths
    (+)        (in networks with nonnegative weights)
    (±)        (in networks with weights that could be negative)
    (DAG)    (in acyclic networks)
    DC        difference constraints
    HP        Hamilton paths
    JS(WD)    job scheduling (with deadlines)

存在负边的最短路径
    存在负边并不可怕,可怕的是有负环,所以我们给现在所要求的最短路径加个限制条件——无负环。提出以下三个问题:
? 无负环的网络中求最短路径
? 负环检测
? 套利交易问题(PKU上有两道习题 1238  2240 )
    回顾Dijkstra方法,它对于负边的情况无法处理,因为作为贪心思想的Dijkstra在面对负边时失去了最优子结构的性质,导致算法失败。
    而Floyd依然可以应对负边的网络,并且Floyd可以在V3的复杂度下检测负环。
    替代Dijkstra的方法是Bellman-Ford算法,可以在复杂度VE下检测负环和求出单源点最短路径。具体算法随便找本算法就能找到。
   对边重新赋权(reweighting)不影响最短路径!
    多么振奋人心啊,如果对边进行重新赋值不影响最短路径,那么我们可以消除所有的负边,这样不就又回到了无负边的网络了吗。
    首先会想到的重新赋边权的算法是给每条边加一个常数值,但事实证明此法不通。简单的反例是a到b有两条路径,一条经过一个点长A,一条经过两个长B (<A)。再给边加过常量C后,前面一条的路径总长增加了C,后面增加了2C,若C>A-B,那么最短路径将从后面那条变为前面那条。明显影 响了最短路径。
    正确的重新赋权法是在计算了一次任意点的Bellman-Ford后,对每条边加一个该边两端点的差值的方法。其原理是利用对于任意的那一点的相对性来保持权的一些特性。具体的我就不罗嗦了,仔细推导一下就可以知道了。
    重新赋权法如下,其中wt是经过一次Bellman-Ford后的单源点最短路径,edge.w是边的另一端:
        对于每一个点v:
            对于从点v出发的所有边edge:
                edge.weight = edge.weight + wt[v] - wt[edge.w];
   由于改变了边的权值,关于要用到比较边大小的操作将全部失灵!补救方法是在计算完最短路径后,将权改回来。另外,该法仍然无法解决负环的问题。
    既然有了重新赋权值的方法,解决所有点最短路径又有了新的算法(Johnson's algorithm):
? 对网络的源点(或任意点)运行Bellman-Ford算法
? 检测到负环则停止
? 对网络重新赋权值
? 用Dijkstra算法求所有点最短路径。
    Johnson 算法的复杂度是VElogdV,d=E/V,若E<2V,则d=2。
    最后,对于无负边网络情况下,检测环比单源点最短路径简单、单源点最短路径比所有点最短路径简单。而在带负边的情况下,三个问题的最坏情况下的复杂度是一样的。
链接:
PKU 1238:
   http://acm.pku.edu.cn/JudgeOnline/problem?id=1238

PKU 2240:
   http://acm.pku.edu.cn/JudgeOnline/problem?id=2240

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=715625
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值