Bellman-Ford算法与队列优化模板

Bellman-Ford模板

  • 问题:有 n 个点,m 条边的正权图有向图中,求源点 s 到其余各点的最短路径

算法流程

  • 循环n - 1次
  • 每次循环枚举所有 a->b 权值为 w 的边,判断 dis[b] = min(dis[b],dis[a] + w);

【Code】

const int N,M;//N最大点数,M最大边数
struct Edge{
    int a,b,w;//边 a -> b 权值为 w 
}p[M];//因为每次都是枚举所有的边,所以可以直接这样存边,无需邻接矩阵 or 邻接表
int n,m;
int dis[N];//表示源点 s 与其余各点的距离

void Bellman_Ford(int s){
    fill(dis + 1,dis + n + 1,inf);
    dis[s] = 0;

    for(int i = 0;i < n - 1;++ i){
        for(int j = 0;j < m; ++ j){
            int a = p[j].a,b = p[j].b,w = p[j].w;
            dis[b] = min(dis[b],dis[a] + w);
        }
    }
}
  • 为什么是循环 n - 1 次呢?

因为,在无负权环图中,任意两点间的最短路径,一定最多只含有n - 1条边。

显然,Bellman-Ford算法的时间复杂度是O(nm)的,此外,该算法还能判断图中是否含有负环。在循环了n - 1 次后,第n次循环如果还可以将dis数组变小,说明两点之间的最短距离有了n条边,这就与之前结论冲突了,所以这条路径中一定含有负权环。

队列优化的Bellman-Ford算法(SPFA算法)

仔细观察上述算法,不难发现,遍历所有边时,如果这个边的出边点未更新,则入边点的最短距离是不会更新的(既 dis[b] = min(dis[b],dis[a] + w); 这句话是无效的),由此,我们用队列来对已更新的出边点进行存储,每次只遍历已更新点的出边,删去了Bellman-Ford算法中对无效边的扫描,这样就将O(nm)的时间复杂度降低至了O(km),其中k是一个很小的常数,所以在一般情况下,我们可以将SPFA算法时间复杂度视为线性的。

  • tips:极端情况下SPFA算法的时间复杂度还是O(nm)的(一些竞赛题会专门卡SPFA算法),所以要慎用,正权图尽量使用堆优化的Dijkstra算法。
const int N,M,inf;//N代表最大点数,M代表最大边数,inf代表正无穷

//数组模拟邻接表存图
int h[N],e[M],w[M],ne[M],idx = 1;
int n,m,s;//s为源点
int dis[N];//表示源点 s 与其余各点的距离
bool st[N];//判断该点是否在队列中

//存入 a -> b 的单向边,权值为 c
void add(int a,int b,int c){
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx ++;
}

void SPFA(int s){
    //初始化
    fill(dis + 1,dis + n + 1,inf);
    dis[s] = 0;

    //创建队列
    queue<int> q;
    q.push(s);//在初始化时源点已更新,入队
    st[s] = 1;//源点在队列中,进行标记

    while(!q.empty()){
        //取出并删去队首
        int t = q.front(); q.pop();

        //队首已经不在队列里,进行标记
        st[t] = 0;

        //循环遍历队首 t 的所有出边
        for(int i = h[t]; i ;i = ne[i]){
            int j = e[i];
            //如果可以更新
            if(dis[j] > dis[t] + w[i]){
                //更新
                dis[j] = dis[t] + w[i];
                //判断被更新的点是否在队列里,如果不在,就入队并标记
                if(!st[j]) q.push(j),st[j] = 1;
            }
        }
    }
    
}

当然,SPFA算法也是可以判断负环的,额外开一个 cnt[] 数组,在更新 dis 的值时,加一句 cnt[j] = cnt[t] + 1; ,表示源点到点 j 的最短距离边数是源点到点 t 的最短距离边数+1,当 cnt[j] >= n 时,既代表源点到点 j 的最短距离有n条边,就可以判断这条最短距离路上含有负权环。


2020.08.06

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值