求单源最短路时,当存在负权边时,使用Bellman_ford 和 SPFA算法
对于存在负权回路需要进行判断。当有边数限制时,即使存在负权回路也是可以解的,但是没有边数限制那么就无解,答案为负无穷大。
Bellman_ford
算法流程:( n 为点数,m 为边数)(O(n*m))
- 循环 n 次
- n 次循环中,循环边数 m 次,找最短边
对于有边数限制,需要一个备份数组,存储上一次的路径,避免发生路径串联(会在代码中体现)
SPFA
全称:Shortest Path Fast Algorithm
给定一张有向图,若对于图中的某一条边(x,y,z),有 dist[y] <= dist[x] + z 成立(dist表示最短路径),则称该边满足三角形不等式;若所有的边都满足三角形不等式,则 dist 数组就是所求的最短路。
SPFA算法流程:
- 建立一个队列,最初队列中只有起点1
- 取出队头元素,扫描它的所有出边(x,y,z),若不满足三角形不等式,则更新 dist[y],同时若 y 不在队列中,则将 y 入队
- 重复操作,直至队列为空
SPFA算法是 Bellman_ford 的优化,在任意时刻,队列中都保存了待扩展的点(也就是说队列中保存的点都以及完成了更新,使其满足三角形不等式,设该点为 y ,那么 dist[y] 就是起点到 y 的最短路;然后使用这个最短的特性去更新其他不满足三角形不等式的点。这就是和dij不同的地方,dij是使用一个小根堆,每次取出的点是最短路),每次入队相当于完成了一次 dist 数组更新操作,使其满足三角形不等式。一个点可能多次入队、出队。最终所有节点收敛到全部满足三角形不等式。这个队列避免了 Bellman_ford 算法中对不需要扩展的点的冗余扫描。时间复杂度是O(m),但是在特殊构造的图中,可能退化到O(n*m)
Bellman_ford代码实现
#include<iostream>
#include<cstring>
using namespace std;
const int N = 510, M = 10010, INF = 0x3f3f3f3f;
int n, m, k;
struct edge
{
int a, b, w;
}edges[M];
int dist[N], backup[N];
void bellman()
{
memset(dist, 0x3f, sizeof dist); // 由于有边数限制,所以需要备份数组,避免路径串联
dist[1] = 0;
for(int i = 0; i < k; i++) // 这有路径限制 k 条
{
memcpy(backup, dist, sizeof backup);
for(int j = 1; j <= m; j++)
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
dist[b] = min(dist[b], backup[a] + w);
}
}
// 当存在负权回路时,当起点到不了 n 时,dist[n] = INF,但是如果 n-1 点到 n 点的权为负数,那么 dist[n] 会小于INF,所有当 dsit[n] 大于一个比较大的数时,就不可达
if(dist[n] > INF / 2) cout << "impossible" << endl;
else cout << dist[n] << endl;
}
SPFA代码实现
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N = 1e5 + 10;
int h[N], e[N], w[N], ne[N], idx;
int dist[N];
bool st[N]; // 记录点是否在队列中
int n , m;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void spfa()
{
memset(dist, 0x3f, sizeof dist);
queue<int> qu;
dist[1] = 0;
st[1] = true;
qu.push(1);
while(!qu.empty())
{
int t = qu.front(); qu.pop(); st[t] = false;
for(int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if(dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if(!st[j])
{
qu.push(j); // 当该点满足三角形不等式,且不在队列中;将其入队,去扩展其他不满足的点
st[j] = true;
}
}
}
}
}