HELLO WORLD--一起加油(�|

kingwzun

园龄:3年6个月粉丝:111关注:0

2022-08-05 19:53阅读: 405评论: 0推荐: 0

图论 _ 含负权边的最短路

松弛:

  • 考虑节点u以及它的后继节点v。从起点跑到v有好多跑法,有的跑法经过u,有的不经过。

  • 经过u的跑法的距离就是dist[u]+wu>v

  • 松弛操作就是比较dist[v]dist[u]+wu>v哪个大。

  • 如果前者大一点,就说明当前的不是最短路,就要赋值为后者,这就叫做松弛

Bellman - Ford算法

Bellman - Ford算法用于求带负权值的图的最短路

时间复杂度 O(nm)

一般来说spfa算法各项性能都是优于Bellman - Ford算法。

只有个别题(后面应用的时候会说),只能用Bellman - Ford算法来做。

算法理解

伪代码:

for n次
for 所有边(a,b,w)
dist[b] = min(dist[b],last[a] + w)//last是指上一次循环的dist

解释:
假设 1 号点到 n 号点是可达的,每一个点同时向指向的方向出发,更新相邻的点的最短距离,

  • 通过循环 n-1 次操作,若图中不存在负环,则 1 号点一定会到达 n 号点,
  • 若图中存在负环,则在 n-1 次松弛后一定还会更新

如果图中有负环,则无法得出结果,否则就得到最短路。
这个博客有图解:https://www.cnblogs.com/gaochundong/p/bellman_ford_algorithm.html
看完图解应该就明白了,很简单算法。

然后有个问题是为什么使用last,而不直接用dist更新?
为了避免串联
如下图:在边数限制为一条的情况下,节点3的距离应该是3,

  • 如果 利用本轮更新的节点2更新了节点3的距离,所以现在节点3的距离是2,结果错误。
  • 如果用上轮的dist(就是last)更新j就没这个问题。
    image

代码

struct Edge{
int a,b,w;
}edges[N];
int n, m,k;
int dist[N];
int last[N];
void Bellman_Ford(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<k;i++){
memcpy(last,dist,sizeof dist);
for(Edge e : edges){
dist[e.b]=min(dist[e.b],last[e.a]+e.w);
}
}
}
void solve()
{
cin >> n >> m>>k;
for (int i = 0; i < m; i++)
{
cin>>edges[i].a>>edges[i].b>>edges[i].w;
}
Bellman_Ford();
if (dist[n] > 0x3f3f3f3f / 2) puts("impossible");
else printf("%d\n", dist[n]);
}

为什么判断结果存在是:dist[n]>0x3f3f3f3f/2, 而不是dist[n]>0x3f3f3f3f
比如说:只有节点x到终点en,而且这条边的权值是负数;
如果起始点be到节点x的距离是无穷大(0x3f3f3f3f),那么起始点be到终点en最短路的权值是小于0x3f3f3f3f的,但是实际上并不存在最短路,所以判断条件不能是0x3f3f3f3f。

应用

一般来说spfa算法各项性能都是优于Bellman - Ford算法。
bellman - ford算法擅长

1.有边数限制的最短路问题

2.带负环的最短路问题

负环:

如果图中存在负环,则最短路不一定存在。
因为如果在负环中一直转,则最短路长度是负无穷...
但是如果负环不能到达终点,或者题目限制最短路的长度,则负环存在也不影响最短路。

负环让最短路变成负无穷的关键是:在负环中一直转
因为Bellman - Ford算法其循环的次数是有限制的(就是n次循环),所以对负环免疫。

SPFA算法

SPFA算法是Bellman ford算法的一个优化。

复杂度:
一般情况下算法复杂度是O(m)
但是最坏情况下和Bellman ford算法一样,都是O(nm).

算法理解

Bellman_ford算法每次会遍历所有的边,但是很多的遍历没有意义,SPFA就是每次只遍历有意义的边。

我们可以想到: 有意义的的遍历只有那些到源点距离变小的点。
因为只有当一个点的前驱结点更新了,该节点才会得到更新;
所以我们创建一个队列将距离被更新的结点加入队列,再用加入队列的节点重复发上述操作。

代码

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible。

int n, m;
int to[M], w[M], pre[M], h[N], idx;
void add(int a, int b, int c)
{
to[idx] = b, w[idx] = c, pre[idx] = h[a], h[a] = idx++;
}
int vis[N], dist[N];
void SPFA(int be, int en)
{
memset(dist, 0x3f, sizeof dist);
queue<int> q;
q.push(be);
dist[be] = 0, vis[be] = true;
while (q.size())
{
int k = q.front();
q.pop();
vis[k] = false;
for (int i = h[k]; i != -1; i = pre[i])
{
int j = to[i];
if (dist[j] > dist[k] + w[i])
{
dist[j] = dist[k] + w[i];
if (!vis[j])
{
vis[j] = true;
q.push(j);
}
}
}
}
if (dist[en] >= 0x3f3f3f3f)
cout << "impossible" << endl;
else
cout << dist[en] << endl;
}
void solve()
{
memset(h, -1, sizeof h);
cin >> n >> m;
while (m--)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
SPFA(1, n);
}

为什么Bellman_ford算法里最后return-1的判断条件写的是dist[n]>0x3f3f3f3f/2;而spfa算法写的是dist[n]==0x3f3f3f3f
因为:

  • Bellman_ford算法会遍历所有的边,因此不管是不是和源点连通的边它都会得到更新;
  • 但是SPFA只会遍历与源点连通的节点,因此如果终点和源点不连通,它不会得到更新,还是保持的0x3f3f3f3f。

应用

Bellman_ford算法可以在负权回路的图上跑。,是因为其循环的次数是有限制的(就是n次循环)
但是SPFA算法不可以,由于用了队列来存储,只要发生了更新就会不断的入队,陷入死循环。

  1. 也能解决权值为正的图的最短距离问题,且一般情况下比Dijkstra算法还好
    (但是不要尝试,出题人可能会设计特殊样例卡掉)

  2. 含负权边的单源最短路

3. 求负环

方法:
用一个cnt数组记录每个点到源点的边数,一个点被更新一次就+1,一旦有点的边数达到了n那就证明存在了负环。

代码:

int n, m;
int to[M], w[M], pre[M], h[N], idx;
void add(int a, int b, int c)
{
to[idx] = b, w[idx] = c, pre[idx] = h[a], h[a] = idx++;
}
int vis[N], dist[N];
int cnt[N];
bool SPFA(int be, int en)
{
// memset(dist, 0x3f, sizeof dist);
queue<int> q;
//因为负环不一定和起始点相连 ,所以开始将所有点都放入队列
for (int i = 1; i <= n; i++)
{
vis[i] = true;
q.push(i);
}
while (q.size())
{
int k = q.front();
q.pop();
vis[k] = false;
for (int i = h[k]; i != -1; i = pre[i])
{
int j = to[i];
if (dist[j] > dist[k] + w[i])
{
dist[j] = dist[k] + w[i];
cnt[j] = cnt[k] + 1;
if (cnt[j] >= n)
return true;
if (!vis[j])
{
vis[j] = true;
q.push(j);
}
}
}
}
return false;
}
void solve()
{
memset(h, -1, sizeof h);
cin >> n >> m;
while (m--)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
if (SPFA(1, n))
puts("Yes");
else
puts("No");
}

本文作者:kingwzun

本文链接:https://www.cnblogs.com/kingwz/p/16555604.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   kingwzun  阅读(405)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起