Leet|单源点求最短路径的三种常用的方法

最短路径问题

主要参考于博客:
Dijkstra : https://blog.csdn.net/qq_35644234/article/details/60870719
Floyd : https://www.jianshu.com/p/f73c7a6f5a53
SPFA : https://blog.csdn.net/qq_35644234/article/details/61614581
问题描述:
从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径
三种常用的算法: 1. Dijkstra 算法
适用范围 : 无负边,因为有负边,可能会先确定了某个点的单源点的最短距离,但后面会遍历到一条负边来使该距离更短,从而影响了正确答案
方法:
【Leet|单源点求最短路径的三种常用的方法】贪心策略,声明一个数组 dis 来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合:T
初始时,原点 s 的路径权重被赋为 0 (dis[s] = 0)。若对于顶点 s 存在能直接到达的边(s,m),则把dis[m]设为w(s, m),同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大。初始时,集合T只有顶点s。
然后,从dis数组选择最小值,则该值就是源点s到该值对应的顶点的最短路径,并且把该点加入到T中,OK,此时完成一个顶点,然后,我们需要看看新加入的顶点是否可以到达其他顶点并且看看通过该顶点到达其他点的路径长度是否比源点直接到达短,如果是,那么就替换这些顶点在dis中的值。
然后,又从dis中找出最小值,重复上述动作,直到T中包含了图的所有顶点。
时间复杂度 :O(V^2 + E)
可以用优先队列来优化 每次选择最短路径的点的操作 来到时间复杂度 O((V+E) * lg V)
用STL中的优先队列实现堆:
while(优先队列Q(V)非空)
-->队头出队,松弛它的边E
-->松弛了的<新距离,点>入队
堆优化代码段:
void dijkstra() { vector st(n + 1); // 用于标记某个点是否已经在队列当中 priority_queue, greater> heap; // 小顶堆,每次 pop 出距离最小的点 dist[n] = 0; heap.push({0, n}); // 进入队列 while (heap.size()) { PII now = heap.top(); heap.pop(); int ver = now.second, distance = now.first; if (st[ver]) continue; // 标记该点是否已经得到了最小距离 st[ver] = true; for (auto &v : g[ver]) { int y = v.first, w = v.second; if (dist[y] > distance + w) { dist[y] = distance + w; heap.push({dist[y], y}); } } } }

Leet|单源点求最短路径的三种常用的方法
文章图片

2. Floyd 算法
算法的特点:
弗洛伊德算法是解决任意两点间的最短路径的一种算法,可以正确处理有向图或有向图或负权(但不可存在负权回路)的最短路径问题,同时也被用于计算有向图的传递闭包。
Leet|单源点求最短路径的三种常用的方法
文章图片

Leet|单源点求最短路径的三种常用的方法
文章图片

路径输出 :
Pij = k 表示 Ai - > Aj 的最短路径需要经过 点 k
对于输出路径 可以用 递推 或者用 栈 来实现
Leet|单源点求最短路径的三种常用的方法
文章图片

时间复杂度 : O(N^3)
3. SPFA算法
它的原理是 :
对图进行V-1次松弛操作,得到所有可能的最短路径。其优于迪科斯彻算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达 O(VE)。但算法可以进行若干种优化,提高了效率。
算法思路:
我们用数组dis记录每个结点的最短路径估计值,用邻接表或邻接矩阵来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止
我们要知道带有负环的图是没有最短路径的,所以我们在执行算法的时候,要判断图是否带有负环,方法有两种:
  1. 开始算法前,调用拓扑排序进行判断(一般不采用,浪费时间)
  2. 如果某个点进入队列的次数超过N次则存在负环(N为图的顶点数)
每次选取队列中的点来更新 dis 数组,直至队列为空
Leet|单源点求最短路径的三种常用的方法
文章图片


代码如下 :
vector st(n + 1,false); // 用于记录是否在队列中 queue q; q.push(n); while(!q.empty()) { int i = q.front(); q.pop(); st[i] = false; for(auto& t : g[i]) { int j = t.x; int w = t.y; if(min_dis[i] + w < min_dis[j]) { min_dis[j] = min_dis[i] + w; if(!st[j]) { q.push(j); st[j] = true; } } } }


总结:
总的应用场景上:
如果是稠密图,Dijkstra+heap比SPFA快,稀疏图则是SPFA比较快
如果求最短路径,则SPFA+BFS比较快,如果是负环检测,则是SPFA+DFS比较快。


    推荐阅读