Bellman-Ford

Bellman-Ford是一种简单的求单源最短路的算法,虽然复杂度较高,但适用范围广,因此不失为一种实用的算法 最重要的是好写好理解


主要思想是每次枚举每一条边,更新终点的最短路
那么枚举多少次才全部更新完呢
答案是n-1

简单证明:
从源点到图上任意一点的最短路径一定不经过环,所以最短路径最多包含n-1条边
枚举n-1次所有边相当于,枚举构成从源点到一个点最短路径的边的所有可能
(因为所有点的最短路径都可以这么来求,我们顺便把其他点的都求了)

360百科证明:
首先指出,图的任意一条最短路径既不能包含负权回路,也不会包含正权回路,因此它最多包含|v|-1条边。
其次,从源点s可达的所有顶点如果 存在最短路径,则这些最短路径构成一个以s为根的最短路径树。Bellman-Ford算法的迭代松弛操作,实际上就是按每个点实际的最短路径[虽然我们还不知道,但它一定存在]的层次,逐层生成这棵最短路径树的过程。
注意,每一次遍历,都可以从前一次遍历的基础上,找到此次遍历的部分点的单源最短路径。如:这是第i次遍历,那么,通过数学归纳法,若前面单源最短路径层次为1~(i-1)的点全部已经得到,而单源最短路径层次为i的点,必定可由单源最短路径层次为i-1的点集得到,从而在下一次遍历中充当前一次的点集,如此往复迭代,[v]-1次后,若无负权回路,则我们已经达到了所需的目的–得到每个点的单源最短路径。[注意:这棵树的每一次更新,可以将其中的某一个子树接到另一个点下]

说Bellman-Ford适用范围广,是因为有负权边可以用,有负权环也可以用
循环次数是固定的,不用担心会爆


Bellman-Ford的一个应用就是求负环

负环定义:一个边权之和为负的环 我也不知道对不对

我们跑完最短路之后,再枚举每一条边,如果还有点可以更新,说明出现了负环
(想象一下跑在一个负环上,越跑距离反而越小,跑到机子瘫了也跑不出最小值)

例题

洛谷 P3385 【模板】负环

题目描述
暴力枚举/SPFA/Bellman-ford/奇怪的贪心/超神搜索

寻找一个从顶点1所能到达的负环,负环定义为:一个边权之和为负的环。

输入输出格式
输入格式:
第一行一个正整数T表示数据组数,对于每组数据:

第一行两个正整数N M,表示图有N个顶点,M条边

接下来M行,每行三个整数a b w,表示a->b有一条权值为w的边(若w<0则为单向,否则双向)

输出格式:
共T行。对于每组数据,存在负环则输出一行"YE5"(不含引号),否则输出一行"N0"(不含引号)。

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#define N 2005
#define M 6005//m最好开两倍
#define INF 10005
using namespace std;
int t,n,m;
int u,v,w;
int cnt;
int dis[N],vis[N],head[N];
struct Edge
{
    int u,v,w,next;
}e[M];
inline int read()
{
    char c;
    int x=0,f=1;
    do{
        c=getchar();
        if(c=='-')
            f=-1;
    }while(c<'0' || c>'9');
    do{
        x=(x<<3)+(x<<1)+(c^48);
        c=getchar();
    }while(c>='0' && c<='9');
    return f*x;
}
inline void addedge(int u,int v,int w)
{
    cnt++;
    e[cnt].u=u;
    e[cnt].v=v;
    e[cnt].w=w;
    e[cnt].next=head[u];
    head[u]=cnt;
}
inline void bfs1()//记下所有1能到的点
{
    queue<int> q;
    q.push(1);
    vis[1]=1;
    while(!q.empty())
    {
        int x=head[q.front()];
        q.pop();
        while(x>0)
        {
            int y=e[x].v;
            if(!vis[y])
            {
                vis[y]=1;
                q.push(y);
            }
            x=e[x].next;
        }
    }
}
inline bool Bellman_ford()
{
    for(int i=1;i<=n;++i)
    {
        head[i]=-1;
        dis[i]=INF;
    }
    dis[1]=0;
    for(int i=1;i<n;++i)
    {
        for(int j=1;j<=cnt;++j)
        {
            if(dis[e[j].v]>dis[e[j].u]+e[j].w)
                dis[e[j].v]=dis[e[j].u]+e[j].w;
        } 
    }
    for(int j=1;j<=cnt;++j)
    {
        if(dis[e[j].v]>dis[e[j].u]+e[j].w && vis[e[j].u])
        //存在负环且1能到负环上的点
            return true;
    }
    return false;
}
int main(){
    t=read();
    while(t--)
    {
        cnt=0;
        memset(vis,0,sizeof(vis));
        n=read(),m=read();
        while(m--)
        {
            u=read(),v=read(),w=read();
            addedge(u,v,w);
            if(w>=0)//w=0也要加两次
                addedge(v,u,w);
        }
        bfs1();
        if(Bellman_ford())
            printf("YE5\n");
        else
            printf("N0\n");
    }
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值