最短路总结

来源:互联网 发布:vscode html 格式化 编辑:程序博客网 时间:2024/06/03 18:04

写个博客记录一下最短路的几种算法,尽量做最正确的解答,减少大家的疑惑,网上有好多讲的都抄来抄去,还有好多讲的都是错误的。。。

熟悉的最短路算法就几种:bellman-ford,dijkstra,spfa,floyd,下面针对这几个算法具体解析一下。首先说明一点,就是关于负环的问题。
bellman-ford可以用于边权为负的图中,图里有负环也可以,如果有负环,算法会检测出负环。
dijkstra只能用于边权都为正的图中。
spfa是个bellman-ford的优化算法,本质是bellman-ford,所以适用性和bellman-ford一样。
floyd可以用于有负权的图中,即使有负环,算法也可以检测出来。

任何题目中都要注意的有四点事项:图是有向图还是无向图、是否有负权边,是否有重边,顶点到自身的可达性。这几点非常重要,可以在我下面的讲解中体会。

1、dijkstra

这个最简单,只能在边权都为正的图中用这个算法,不论是有向图还是无向图。算法是个贪心的过程,每次拿出一个没有被标记过的距离最小的顶点,并从这个点进行扩展,也就是尝试松弛从这个点出发的每条边。为什么一定要用在正权图中呢?因为算法的过程相当于把整个图中的点一个一个加入到“处理完”集合S的过程,并且处理完集合中的点的距离一定是从源点到该点的最小距离。如果图中有负权,会导致一个进入集合中的点可能在后面的过程中距离值变得更小,算法就错了。举个例子来说:有向图
1 2 2
1 3 3
3 2 -2
求点1到其他各个节点的最短路。
根据dijkstra算法,首先会把节点1放入到集合S中,然后更新节点2和3的值,距离分别为d[2]=2,d[3]=3;之后因为节点2的d值比节点3的小,所以把节点2加入到S集合,然后尝试松弛从节点2出发的边,发现没有可以更新的,算法继续;最后拿出节点3,但是此时有一条3到2的权值为-2的边,这样导致了节点2的距离值更小了,算法所维护的集合S的特性被破坏了,算法也就不正确了。所以dijkstra绝对不能用于有负权的图。

2、bellman-ford

这个要讲很多,里面也有我最纠结的思考。
首先说下算法的用途,有向图边权可正可负,求源点到每个点最小距离。算法就是执行了n-1次对所有边的松弛而已。算法基于这样的事实,如果存在最短路,那么最短路中一定没有环。如果有0环,那么把这个0环去掉不影响结果;如果是正权环,那么去掉这个环路径长度更小;如果是负权环,那么最短路径不存在,因为可以走无数次这个环,路径长度会无穷小。为什么执行n-1次循环就可以了,这个证明要看算法导论,这里我简单写一下:
设G=(V,E)是一个源点为s的有向图,权函数为w,假设G中不包含从源点可达的负权回路,那么算法结束时(执行完n-1轮松弛操作),对任意节点v有d[v]=δ(s, v)
证明:对任意s可达的点v,总能找到一条最短路径p=(v0, v1, ..., vk),其中v0=s,vk=v。因为最短路径都是简单路径,那么p至多包含n-1条边,所以k<=n-1。由于v0=s,所以d[s]=δ(s,s)=0,当对所有的边进行第一次松弛后,一定有d[v1]=δ(s,v1)。以此类推,对所有的边进行第k次松弛后,有d[vk]=δ(s,vk)。因此当对所有边进行n-1次松弛之后,必有d[v]=d[vk]=δ(s,vk)=δ(s,v)。然后我们证明为什么当d[v(i-1)]=δ(s,v(i-1))时,对边(v(i-1), vi)松弛之后有d[v(i)]=δ(s,v(i))。由于s->v1->...->vi是一条最短路径,在对边(v(i-1),vi)松弛之后一定有d[vi]<=d[v(i-1)]+w(v(i-1),vi)=δ(s,v(i-1))+w(v(i-1),vi)=δ(s,vi),又由于d[vi]>=δ(s,vi)(这个数组d的性质),所以d[vi]=δ(s,vi)。这个是比较严谨的证明。 网上有这样的描述性证明:算法可以看成个动态规划的过程,也即最多经过k条边的最短路可以由最多经过k-1条边的最短路再添一条边获得。具体来说就是,第一轮松弛代表从s经过最多一条边的可以到达的所有点的最短距离,第二轮松弛代表从s经过最多两条边可以到达的所有点的最短距离,后续略。这样的说法对不对?对!是可以这样子想,但是d数组的意义却不是这样。比如第一轮松弛之后数组d中的值可不是源点可达的经过最多一条边的最短距离,可能包括了经过多条边的最短距离。这是因为我们一直是对d数组进行操作的,在一轮的松弛中,我们用到了本轮之前的松弛操作得到的结果。所以要切记,第k轮操作之后结束的时候,d数组中存储的值并不是最多经过k条边的最短路!!!那么怎么用这个动态规划的思想呢?很简单,只要用两个数组存储记录就行了。虽然按照网上的这种说法,d数组的意义并不正确,但是最终得到正确的结果是没有问题的。

另外容易忽视的一个点是,算法会求出从源点可达的负权环,如果一个负权环从源点不可达,那么算法是求不出来的,不过可以用一个非常小的技巧达到这一点,建立一个超级源点向原图中的每个顶点建立一条边,边权值任意,那么从这个超级源点到原图中的任意点都可达了,就能处理所有的负权环了。

有一个非常纠结的点是,算法隐含了一个条件,一条有向边可以用无数次。这个可能是非常容易忽略的地方,虽然很少有题目会涉及到,但是还是值得思考一下。像我在上面讲的,如果存在一个负权环,那么最短路径不存在,这就隐含了一条边可以走无数次,那如果一条边只能走一次呢?这种情况下所有的最短路算法都不能用了,但是图中任意点的最短路都会存在,怎么求有待思考,我是不会做。。。我猜这个问题是NPC的。此外,求解有向图最短路径是P的,最短简单路径和最长简单路径都是NPC的(简单路径指顶点不能重复走)。如果一个有向图没有负环(可以有负权边),那么这个图的最短简单路径是P的;同理,如果一个有向图没有正环,那么这个图的最长简单路径也是P的。

然后说我最纠结的地方,无向图的最短路怎么求。网上都这样说,无向边拆成正反两条边就行了,其实不是这样的,要看具体的情况。比如给你个无向图,让你求最短路,那你要注意这个图中是否有负权边,如果没有的话,无向边拆成正反两条边是没问题的,但是如果有负权边并且规定一条无向路只能走一次,那拆边就错了,因为拆边之后会认为图中存在负环,最短路不存在,但是依照题意,最短路一定是存在的(枚举所有路径即可),这个问题和上面的有向图的有向边最多只能用一次的最短路问题是同一类问题,目测也是NPC的。很多图论的问题、以及带限制条件的最短路问题都是NPC的。

以下这段话不是针对最短路说的,是针对无向图说的。无向图中只要有负权边,那么一定要从题意角度出发思考问题,没有固定的算法可用。比如两个点一条负权边组成的图算不算含负圈?有些题算,因为这样的定义可以使用很多现有的算法,有些题不算,因为直观上想,只有一条边怎么能算有圈?还有一些题对圈的特性做了一些限制,比如求的是简单圈等等。无向图中的trick有很多(考虑重边,圈,顶点能否重用等等),列举不完,做题目的时候要特别留心注意。

3、spfa

spfa就是个bellman-ford的优化,算法本质和bellman-ford是一样的,具体不多说。有个应用就是可以判负环,当一个点进入队列大于等于n次的时候,就可以认为图中存在负环了,当然spfa能做的bellman-ford都能做,bellman-ford也可以判负环。

4、floyd

floyd算法是非常强大的,可以处理不少问题,复杂度是O(n^3)的,下面解析一下这个算法
不少人可能刚接触floyd的时候非常容易把它写错,错误的写法就是三层循环的从外到内的变量分别为i,j,k,正确的写法应该是k,i,j。写错的原因是不理解floyd算法造成的,那么为什么从顺序是k,i,j呢?

其实floyd的算法本质是个动态规划!dp[k][i][j]代表i到j的中间节点(不包括i和j)都在区间[1,k]时,i到j的最短路。算法的最外层循环是个从小到大枚举k的过程,当最外层刚刚进入第k次循环的时候,我们已经得到了所有点对的dp[k-1][][]的值,也就是所有点对(i,j)的i到j的中间节点都在[1,k-1]区间的i到j的最短路。那么对任意的点对(i,j),如果他俩的最短路经过k点,则dp[k][i][j]=dp[k-1][i][k]+dp[k-1][k][j];如果不经过k点,则dp[k][i][j]=dp[k-1][i][j]。所以当我们求dp[k][][]的时候,要保证所有的dp[i-1][][]都求出来了,因此,最外层循环是k。

floyd能做很多事情,下面简单说两个,求有向图的最小环或者最大环(顶点数>=2),求无向图的最小环(顶点数>=3)。
先说求有向图最小环(最大环略)。有两种方法可以求,一种是设定g[i][i]为无穷大,这样最后找所有的g[i][i]里的最小值就行;另一种是正常做floyd,然后对每个点对(i,j),求g[i][j]+dp[n][j][i]的最小值,这样的原理是最小环如果存在的话,那么可以枚举一个这个环里的边i->j,那么包含这条边的最小的环一定是i->j和dp[n][j][i]构成的最短路。

无向图的最小环做法和有向图不一样,是因为无向边可能会被用两次导致出错,举例说就是:枚举了一条边i->j,然后其与dp[n][j][i]的和作为一个结果,但是如果j到i的最短路就是边j->i的话,那么我们找的环其实只是一条边而已,这样的结果显然是错误的。正确的做法是先判断最小环再更新最短路
0 0
原创粉丝点击