Bellman-Ford(贝尔曼-福特) & SPFA算法

本文详细介绍了两种图论算法——Bellman-Ford算法和SPFA算法,用于寻找带有负权边的图中最短路径。Bellman-Ford算法允许存在负权边,但不能有负权环,可以通过多次松弛操作检测负权环。SPFA算法基于队列实现,结合了广度优先搜索的特点,同样能检测负权环。通过实例代码,阐述了两种算法的实现过程和负权环的判断方法,对于理解和应用图的最短路径算法具有指导意义。
摘要由CSDN通过智能技术生成

Bellman-Ford(贝尔曼-福特) & SPFA算法

Bellman-Ford

一种基于松弛(relax)操作——广度,的最短路算法。

优点

可以有负权边,但是不能出现负权环

相对于迪杰特斯拉算法而言,它的时间复杂度更高,但是能解决负权边问题

检测图中是否有负权值回路

负权边:权重为负数的边。
负权环:源点到源点的一个环,环上权重和为负数。

判断是否存在负权环

//解法:
/*
跑Bellman-Ford算法
如果有点被松弛成功了n次,那一定存在
如果n-1次之内算法就结束了,那一定不存在
*/

//判断是否存在负环
    for(int i=1;i<=cnt;i++){
        if(dis[e[i].u]==inf || dis[e[i].v]==inf)
            continue;
        //在经过第一遍松弛之后,应该得到最小路径,若还能继续松弛,说明存在负权环
        if(dis[e[i].v]>dis[e[i].u]+e[i].w)
            return true;
    }
    return false;

邻接表实现

#include <bits/stdc++.h>

using namespace std;
const int N=2e6+10,inf=1e9+7;
struct node{
    int u;
    int v;//终点
    int w;//权值
}e[N];

int n;//点
int m;
//不一定是边数
//当负权值边时,只有一条边;
//当正权值边时,存在两条边(无向图)
int cnt=0;
int dis[N];

bool bellman_ford(){
    dis[1]=0;//从顶点1出发,
    //dis[s]=0;从顶点s出发
    for(int i = 2; i <= n; i++)
		dis[i] = inf;//除出发点的其余点的移动距离均为inf
    for(int i=1;i<=n-1;i++)//到n-1个点,因为最后一个点是终点
        for(int j=1;j<=cnt;j++)//cnt是真正的边数
            //松弛函数 
            //dis[e[j].v] > dis[e[j].u]+e[j].w
            //   终点           起点    当前点的权值
            if(dis[e[j].u]!=inf && dis[e[j].v] > dis[e[j].u]+e[j].w)
                dis[e[j].v] = dis[e[j].u]+e[j].w;
                
    //判断是否存在负环
    for(int i=1;i<=cnt;i++){
        if(dis[e[i].u]==inf || dis[e[i].v]==inf)
            continue;
        //在经过第一遍松弛之后,应该得到最小路径,若还能继续松弛,说明存在负权环
        if(dis[e[i].v]>dis[e[i].u]+e[i].w)
            return true;
    }
    return false;
}

int main()
{
    int t;
    cin>>t;
    while(t--){
        cnt=0;
        memset(e,0,sizeof(e));
        cin>>n>>m;
        for(int i=1;i<=m;i++)
        {
            int u,v,w;
            cin>>u>>v>>w;
            if(w<0)
                e[++cnt]={u,v,w};
            if(w>=0)
                e[++cnt]={v,u,w},e[++cnt]={u,v,w};;
        }
        if(bellman_ford())
            cout<<"YES"<<endl;//存在负权环
        else
            cout<<"NO"<<endl;
        //dis[n] 从顶点1出发得到的最短路径
        //dis[e] 从顶点s出发得到的最短路径
    }
    return 0;
}

SPFA

思想: 逼近 + 松弛 + bfs

操作

  1. 将源点压入队列
  2. 取出队头元素,对该元素的各邻接边进行松弛操作。如果该点没有在队列中,就压入队列
  3. 重复步骤 1.

判断是否存在负权环

  1. 首先将1~n个顶点都入队,因为题目不是说从顶点1开始到顶点n查找负权环,而是在每个顶点都可能存在负权环。

  2. 定义一个cnt[x]数组来存储每个顶点

    int cnt[N]; //每个顶点的边数
    
  3. cnt[x]:1~x出现的边数,如果出现的边数==n了,就说明到x这个顶点出现了n+1个顶点,在这n个顶点必定有两个顶点是相同的,而我们每次判断负权环是在能够压缩路径的前提下(也就是有更短距离,距离变小的情况下才压缩、判断,所以只会是更小的负权才压缩)这样就存在了一个负权环。

if(dist[v]>dist[u]+w) 
{ 
    dist[v]=dist[u]+w; 
    cnt[v]=cnt[u]+1; //对出边顶点的边数+1
    if(cnt[v]>=n){//如果1~v这个顶点的边数==n,说明有n+1个顶点
		flag=0; 
        break; 
	 }
}
#include <bits/stdc++.h>

using namespace std;
const int N=2e6+10,inf=1e9+7;
struct node{
    int v;//终点
    int w;//权值
    int next;
}edge[N];

queue<int> q;
int n,m;//点,边
int head[N];
int book[N];//标记是否入队
int dis[N];//记录移动
int cnt[N];//每个顶点的边数
int pos=0;
int flag=1;

void add_edge(int u,int v,int w){
    edge[pos].v = v;
    edge[pos].w = w;
    edge[pos].next = head[u];
    head[u] = pos++;
}

void spfa(){
    memset(dis,inf,sizeof(dis));//没去过的点统统标记为无穷
    for(int i=1;i<=n;i++){
        q.push(i);//所有点入队
        book[i]=1;//入队标记
    }
    while(!q.empty()){
        int t = q.front();//弹出
        q.pop();
        book[t] = 0;//出队取消标记
        for(int i=head[t];i!=-1;i=edge[i].next)
        //遍历这个点的所有边
        {
            int to = edge[i].v;
            int weight = edge[i].w;
            //松弛操作,进行压缩
            if(dis[to]>dis[t]+weight){
                dis[to] = dis[t]+weight;
                cnt[to] = cnt[t] + 1;//对该边终点的边数+1
                if(cnt[to]>=n){ //如果1~v这个顶点的边数==n,说明有n+1个顶点
                    flag = 0;
                    break;
                }
                //压缩后,如果在队外,进行入队
                if(!book[to])
                {
                    q.push(to);
                    book[to]=1;
                }
            }
        }
    if(!flag)
        break;
    }
}

int main()
{
    cin>>n>>m;
    memset(head,-1,sizeof(head));
    //memset(dis,inf,sizeof(dis));
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        add_edge(u,v,w);
    }
    spfa();
    if(!flag)
        cout <<"Yes";
    else
      cout <<"No";
    return 0;
}

求最短路(链式前向星)

类似于bfs的广度搜索

#include <bits/stdc++.h>

using namespace std;
const int N=2e6+10,inf=1e9+7;
struct node{
    int v;//终点
    int w;//权值
    int next;
}edge[N];

queue<int> q;
int n,m;//点,边
int head[N];
int book[N];//标记是否入队
int dis[N];//记录移动
int cnt=0;

void add_edge(int u,int v,int w){
    edge[cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}

void spfa(){
    memset(dis,inf,sizeof(dis));//没去过的点统统标记为无穷
    dis[1] = 0;//起始点为0
    q.push(1);//起始点入队
    book[1] = 1;//标记入队
    while(!q.empty()){
        int t = q.front();//弹出
        q.pop();
        book[t] = 0;//出队取消标记
        for(int i=head[t];i!=-1;i=edge[i].next)
        //遍历这个点的所有边
        {
            int to = edge[i].v;
            int weight = edge[i].w;
            //松弛操作,进行压缩
            if(dis[to]>dis[t]+edge[i].w){
                dis[to] = dis[t]+edge[i].w;
                //压缩后,如果在队外,进行入队
                if(!book[to])
                {
                    q.push(to);
                    book[to]=1;
                }
            }
        }
    }
}

int main()
{
    cin>>n>>m;
    memset(head,-1,sizeof(head));
    //memset(dis,inf,sizeof(dis));
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        add_edge(u,v,w);
    }
    spfa();
    if(dis[n]==inf)
        cout <<"impossible";
    else
        cout <<dis[n];
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值