其实具体我有两个问题。
1.我知道最原始的地杰斯特拉算法复杂度是O(n^2),但是用堆优化后,我们不再用一个数组去标记一个节点的最短路径是否已经求出来,而是用队列是否为空作为结束循环的依据。所以堆优化后的地杰斯特拉是否能够用来求带有负权边的最短路径?
2.我觉得用堆之后的dij 其实 就跟spfa类似了,只是两个的思想不同,一个是优先队列,存放距离源点最近的点,而spfa只是一个简单的队列,缩小松弛的节点范围。不知道这样的理解是否正确?
希望大家帮我解决一下,想了很久,网上也没有具体的结论。
你好,Dijkstra算法,带有负权边的情况下无论用不用优先队列都是不可以的,你去画个例子就可以看出来了。而spfa却可以处理。
你也说了Dijkstra算法和spfa的思想不一样,其实spfa,Dijkstra和BFS形式是有点类似。你不能说Dijkstra用了优先队列就和spfa类似了,不用优先队列就不类似了,因为用了优先队列所以形式上有点相似。优先队列只是优化了循环找最小值的方式。
此外spfa算法时间效率不稳定,如果图十分复杂边很多,spfa时间效率就很低了。Dijkstra堆优化时间复杂度很稳定。
非常感谢,终于有回答了。
我自己的看法是,在不存在负权环的情况下,因为dij在常规的O(n^2)算法中用标记数组标记了,一旦节点的最短路径确定,以后不能修改。
但是用了堆,没有使用标记数组,不会影响以后对它的更新。
@Jinke2017: Dijkstra的最小堆,每次出队列之后的节点都是最短路径确定的,是不能被修改的,以后也不能被更新。而spfa出队列之后,还有可能再次被入队列更新。
@Shendu.cc: 不会吧?为什么不能再被更新,根据堆优化后的dij算法,某个节点出队列之后,还是可以被再次加入队列的
@Shendu.cc: 之前常规的O(n^2)是因为找到最短路径后被标记了,才不能被修改。
@Jinke2017: 嗯。你这么想。每次从优先队列出来点u的是不是优先队列中距离源点最小的?然后把遍历u的下个点v,并且入队列,d[v]是肯定大于d[u]的,也就是说从点u出队列之后,再入队列的点x,d[x]都是肯定大于d[u]的,以此类推,后续的点也是如此。如果按你说的u可以再次入队列,那就是形成环的情况,这个时候你计算的新d[u]是肯定比之前的旧d[u]要大的,对不对?既然再次入队列都是路径变大了,又何必入队列?所以出队列的点都是确定的最短路径。你的算法可能需要改进哦,要加一个标记,标记已经出队列的点。
@Shendu.cc: 非常感谢这么详细的说明。但是我觉得,如果存在负边,后面的d[u]可能会比上一次出队列的d[u]要小.我举个例子,比如说 有n=3个节点(编号0 1 2 ),m=3条有向边 map[0][1]=2 map[0][2]=3 map[2][1]=-4,求0到1的最短路径
根据算法的过程,队列的情况是这样 queue[] = {0} 然后更新了1 和2 --> queue[] = {1,2} 然后1出,没有更新 --> queue[] = {2} 然后2出,更新了1 所以1 进队列 --> queue[]={1}
然后1 出,没有更新。 最后得到0到1的最短路径是-1。
@Jinke2017: 嗯,你说的非常有道理,你这段话其实就是spfa算法,而不是Dijkstra算法了,哈哈哈(没忍住。。)。这也是spfa可以处理负权边的原因。
@Shendu.cc: 是啊,我总觉得其实用优先队列后的dij已经不是原来那个dij了,它跟spfa的区别只是 使用队列的想法不同,我这样理解可以吗?
@Jinke2017: 嗯,你可以这么理解。但是Dijkstra的思想一定要明确,就是每次都会找那条最短边,而且Dijkstra无法处理负权边。你想,你如果可以出队列再入队列,那和O(n^2)原始的算法就不是同一个算法了。而至于什么时候用Dijkstra堆优化,什么时候用spfa,是可以实际情况来判断的。
@Shendu.cc: 明白,其实我还有问题,对于spfa的,为什么说当一个节点进队列的次数超过n就存在负权环,我自己揣摩一下,是不是因为它跟bfs原理类似,一个点最多跟其余n-1个节点有边的缘故?最近在总结最短路径,问题有点多,麻烦您帮我解答一下
@Jinke2017: 原理很简单啊,你想负环的话,是不是每走一圈,路径就越短?我要是无限走下去,是不是就无限短。所以在spfa算法里,肯定无限的进入队列,出队列,来谋求最短值。当一个节点入队列大于n,就表示形成了负环。至于为什么超过n次呢,对于一个完全图最多有n-1个点能到第n个点,但是只有1个点的时候,不就入队列1次嘛。所以判断不能超过n次。
@Shendu.cc:不好意思,昨晚太困就去睡了。
SPFA实际上还是一个Bfs的过程。当一个点u被加入队列时,说明这次源点S到u的边数比上次至少多一条。对于一个完全图最多有n-1个点能到第n个点所以当进队大于n-1次就说明有负环。 至于为什么是>n是因为对于一个节点时候 如果还是>n-1那么任意一个单节点的图会被判定为存在负环 综合考虑取>n。
这是我在知乎看到的,我觉得跟您讲的很接近了。
然后我还看到有一种判断回环方法,用dd[i]表示节点i到源点最短路径中间隔的边数,这样每次更新一个节点后,比如用i更新了j,就可以让dd[j]=dd[i]+1; 然后判断dd[j] >n就证明有回环,您觉得这样正确吗?
@Jinke2017: 是正确的,其实原理都差不多。n个点,从源点到任意一点,最多经过几条不重复的边?肯定是n条,如果dd[j]>n表明他走了之前走过的边,并为什么要走呢,因为走了一遭之后发现最短路径又变短了,说明有负边的存在,而且是一个环,且是一个负环。
@Shendu.cc: 明白了,谢谢。