最短路 专题二之 bellman-ford及优化版本的spfa

专题二

在这里插入图片描述

bellman-ford算法适用于存在负权边的稀疏图(有最短边数限制)。

时间复杂度:O(nm)。

bellman-ford算法的步骤如下:
(1)把所有点到源点的距离初始化为∞,把源点到源点的距离初始化为0;

原因:以防有部分点没有连接在大的图上,初始化一下。

(2)遍历 最短路径数量的 次数 去遍历所有的边,更新所有的边用每一条边去更新各点到源点的距离 。

原因:限制了最短路径,那么每一次只遍历和节点相连的点,更新边长。

细节问题
(1) 需要把dist数组进行一个备份,这样防止每次更新的时候出现串联;

  串联的原因:在遍历过程中那些改变状态的点都是与初始状态相连的点,但是在遍历完
  了这些点后,其他点会因为这些点的改变而改变,所以我们需要记录状态,进行备份。

(2) 由于存在负权边,因此return -1的条件就要改成dist[n]>0x3f3f3f3f/2;

  原因:因为有边数的限制所以应该不会是负无穷,但是还是减了,但是又不会太小,所以
  判断为0x3f3f3f3f/2;

(3) 上面所谓的n次遍历的实际含义是当前的最短路径最多有n-1条边,这也就
解释了为啥要i遍历到n的时候退出循环了,因为只有n个点,最短路径无环最多
就存在n-1条边。

(4) 这里无需对重边和自环做单独的处理。

 [1] 重边:由于遍历了所有的边,会遍历到较短的那一条;
 [2] 自环:有边数的限制,所以不会死循环;

(5)每次备份记得还原,然后进行下一步操作。
代码如下:

#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=510,M=1000010;
struct edg {
    
    
    int a,b,w;
} e[M];
int dist[N],back[N];
int n,m,c;
int bellman() {
    
    
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    for(int i=0; i<c; i++) {
    
    
        memcpy(back,dist,sizeof dist);
        for(int j=0; j<m; j++) {
    
    
            int a=e[j].a,b=e[j].b,w=e[j].w;
            dist[b]=min(dist[b],back[a]+w);
        }
    }
    if(dist[n]>0x3f3f3f/2)return -1;
    return dist[n];
}
int main() {
    
    

    scanf("%d%d%d",&n,&m,&c);
    for(int i=0; i<m; i++) {
    
    
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        e[i]= {
    
    x,y,z};
    }
    int t=bellman();
    if(t==-1)puts("impossible");
    else cout<<t<<endl;
    return 0;
}

spfa算法:
在bellman-man算法的改进版:
看内容(2),每一次都要遍历所有的点,但是实际上只要遍历与改变点相连的点即可。
那么我们可以用队列来进行优化。
时间复杂度由之前的O(nm)到最大的O(nm)最小的O(n)。

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=1e5+10;
#define fi first
#define se second
typedef pair<int,int> PII;//到源点的距离,下标号
int h[N],e[N],w[N],ne[N],idx=0;
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++;
}
int spfa() {
    
    
    queue<PII> q;
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    q.push({
    
    0,1});
    st[1]=true;
    while(q.size()) {
    
    
        PII p=q.front();
        q.pop();
        int t=p.se;
        st[t]=false;//从队列中取出来之后该节点st被标记为false,代表之后该节点如果发生更新可再次入队
        for(int i=h[t]; i!=-1; i=ne[i]) {
    
    
            int j=e[i];
            if(dist[j]>dist[t]+w[i]) {
    
    
                dist[j]=dist[t]+w[i];
                if(!st[j]) {
    
     //当前已经加入队列的结点,无需再次加入队列,即便发生了更新也只用更新数值即可,重复添加降低效率
                    st[j]=true;
                    q.push({
    
    dist[j],j});
                }
            }
        }
    }
    if(dist[n]==0x3f3f3f3f) return -1;
    else return dist[n];
}
int main() {
    
    
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    while(m--) {
    
    
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    int res=spfa();
    if(res==-1) puts("impossible");
    else printf("%d",res);
    return 0;
}

注意:
利用bellman-ford算法后
图中每一条边都满足:

a------->b权值为w
dist  [b]  <=  dist [a] + w.

称为三角不等式

bellman-man算法适用于存在负权边的稀疏图(有最短路径限制)
而spfa适用于大部分图的问题。

猜你喜欢

转载自blog.csdn.net/weixin_51626694/article/details/117442255