算法名称 | 能否处理负权边 | 时间复杂性 |
Dijkstra | 因为核心算法是贪心, 所以不能处理负边权 | o(V^2) o(VlogV)Heap+邻接表 |
Bellman—Ford | 能,只要不是负环即可 | o(VE)邻接表 |
SPFA | 能,本质是队列优化的 Bellman—Ford, 负环只会增加出入队列次数 | o(km)~o(VE) k为常数,通常不超过2 |
-
Dijkstra
每次确定了到一个点的最短距离(当前收录),再用该点更新到其它点的距离。
保证路径是按照递增(非递减)顺序生成
//1:后收录的顶点不会影响先收录顶点的距离。(因为后收录的顶点距离一定比先收录的顶点距离大,只会增大路径长度)因此收录集中每个顶点的最短路都已确定。
//2:每次收录顶点只会影响其邻接点的距离。(因为此时未收录的邻接点{B}距离都比本次收录的A大,只会增加到该点的路径长度,不是最短路)
因为需要路径按照非递减顺序生成,因此如果存在不与源点直接连通的负边情况,且负边权足够小,则可能让之前不够短的路径变最短,之前的收录就无效了,需要重新计算。
-
Bellman-Ford
每次对所有边松弛(试探每条边对其邻接边的影响,减短则更新),重复更新N-1轮(防止后更新的边对先更新边的影响)。
若某轮操作后,没有边被更新,说明数组d中所有值都已达到最优,
提前退出可以稍微加快一点速度。
伪代码:
具体实现:
最后再进行一轮操作,若某条边仍然能被更新,则说明图中有从源点可达的负环。
否则,则已达最优。
若需要用Bellman-Ford算法统计最短路径条数,
由于每次对所有边进行操作,所以会反复累计前驱点,
因此存储前驱的数组最好使用set<int> pre[MAXN]
-
SPFA
注意到每条边只可能影响其邻接边,不需要遍历所有边的邻接边,只需要遍历该边的邻接边即可,
因此使队列操作成为了可能。
每次将收到受到影响的邻接边入队,下一次轮到其出队看看其影响会不会传递到下一条邻接边中,
直到队列为空(不再存在需要传递的影响)
伪代码:
具体实现:
如果某个顶点的入队次数超过N-1,说明图中存在从源点可达的负环
如果负环从源点不可达,则需要
- 添加一个辅助顶点C
- 添加一条从源点到达C的有向边
- N-1条从C到达除源点外各顶点的有向边
才能判断负环是否存在
时间复杂度是O(kE),在很多情况下k不超过2,可见该算法在大部分数据时异常高效,
且经常性地优于堆优化的Dijkstra算法。
但若图中有从源点可达的负环,传统SPFA的时间复杂度就会退化成O(VE)
上面代码中的FIFO队列,
可以替换成优先队列priority_queue(SLF优化),
或者双端队列deque(LLL优化)
效率至少提高50%
上面使用队列的是SPFA的BFS版本,
若将队列替换成栈,则可以实现DFS版本的SPFA,
对判环有奇效