这两个算法用于解决存在负权边的单源最短路问题。其中SPFA算法是Bellman-Ford算法的优化。尽管可以用于负权图中,但是对于存在负权环的图两个算法无法有效工作,但是可以利用这两个算法判断图中是否存在负权环。
1.算法流程我们用数组dis记录每个结点的最短路径估计值,用邻接表或邻接矩阵来存储图G。
回忆一下我们 *** 作Bellman-Ford算法,核心的 *** 作是
对于图中的每条边uv:如果 u 到源点的距离 d 加上边的权值 w 小于 v 到源点的距离 d,则更新 v 到源点的距离值 d
可以写成如下的形式
d
i
s
[
v
]
=
m
i
n
(
d
i
s
[
v
]
,
d
i
s
[
u
]
+
w
)
dis[v] = min(dis[v],dis[u] + w)
dis[v]=min(dis[v],dis[u]+w)
初始时,所有的dis[i]都是正无穷。可以看到,dis[v]在未更新时是不变的,w也是固定的。如果dis[u]没有产生变化,那么dis[v]将不会产生变化。最开始状态下,由起点的距离dis[起点] = 0,带来了最初始的变化,受到初始起点影响的节点也产生了变化并影响其他节点。所以我们只需要将产生变化的节点存进一个队列中,并用这个节点检查每个与之相连的节点的距离,如果相连节点的距离产生变化,则同样入队。
总结一下,我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛 *** 作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛 *** 作,直至队列空为止
我们要知道带有负环的图是没有最短路径的,所以我们在执行算法的时候,要判断图是否带有负环,方法有两种:
开始算法前,调用拓扑排序进行判断(一般不采用,浪费时间)
如果某个点进入队列的次数超过N次则存在负环(N为图的顶点数)
取出队头元素,对其能到达的节点进行遍历,更新他们的长度,如果被更新并且还未在队列中,则节点元素入队。
同样进行 *** 作,取出队头3,进行更新
后面就是这样进行,即可完成该算法。
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。 请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible。 数据保证不存在负权回路。 输入格式 第一行包含整数 n 和 m。 接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。 输出格式 输出一个整数,表示 1 号点到 n 号点的最短距离。 如果路径不存在,则输出 impossible。 数据范围 1≤n,m≤10^5, 图中涉及边长绝对值均不超过 10000。 输入样例: 3 3 1 2 5 2 3 -3 1 3 4 输出样例: 2
#include2.spfa判断负环#include #include using namespace std; const int N = 1e5 + 10; int d[N],h[N],idx,e[N],ne[N],w[N]; //st用来记录该节点是否已经在队列中 bool st[N]; queue q; void add(int a, int b, int x) { e[idx] = b; ne[idx] = h[a]; w[idx] = x; h[a] = idx ++; } int main() { int n,m; cin >> n >> m; q.push(1); memset(d,0x3f,sizeof d); memset(h,-1,sizeof h); d[1] = 0; st[1] = true; for(int i = 0; i < m; i ++) { int a,b,x; cin >> a >> b >> x; add(a,b,x); } for(int i = 0; i < n; i ++) { while(q.size()) { auto t = q.front(); q.pop(); st[t] = false; for(int i = h[t]; i != -1; i = ne[i]) { if(d[e[i]] > d[t] + w[i]) { d[e[i]] = d[t] + w[i]; //如果更新了节点并且该节点没有在队列中,则入队 if(!st[e[i]]) { st[e[i]] = true; q.push(e[i]); } } } } } if(d[n] > 0x3f3f3f3f / 2)cout << "impossible"; else cout << d[n]; }
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。 请你判断图中是否存在负权回路。 输入格式 第一行包含整数 n 和 m。 接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。 输出格式 如果图中存在负权回路,则输出 Yes,否则输出 No。 数据范围 1≤n≤2000, 1≤m≤10000, 图中涉及边长绝对值均不超过 10000。 输入样例: 3 3 1 2 -1 2 3 4 3 1 -4 输出样例: Yes
#include参考资料#include #include using namespace std; const int N = 1e4 + 10; int h[N],e[N],ne[N],idx,w[N],cnt[N],d[N]; bool st[N]; void add(int a, int b, int x) { e[idx] = b; ne[idx] = h[a]; w[idx] = x; h[a] = idx ++; } int main() { int n,m; cin >> n >> m; memset(h,-1,sizeof h); //这里不需要将d初始化,因为我们不关系最后的距离 for(int i =0; i < m; i ++) { int a,b,x; cin >> a >> b >> x; add(a,b,x); } queue q; //这里要把所有起点全部入队,因为我们不知道哪一个点的路径会经过负环回路 for(int i = 1; i <= n; i ++) { st[i] = true; q.push(i); } while(q.size()) { auto t = q.front(); q.pop(); st[t] = false; for(int i = h[t]; i != -1; i = ne[i]) { if(d[e[i]] > d[t] + w[i]) { d[e[i]] = d[t] + w[i]; //cnt用来记录到当前节点经过的边的数量 cnt[e[i]] = cnt[t] + 1; if(cnt[e[i]] >= n) { cout << "Yes"; return 0; } if(!st[e[i]]) { q.push(e[i]); st[e[i]] = true; } } } } cout << "No"; }
- 最短路径问题—SPFA算法详解
- Acwing
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)