Bellman-Ford算法详讲

本文深入探讨了Bellman-Ford算法及其优化版SPFA,针对存在负权边的有向图求解单源最短路径问题。详细解释了算法原理、流程,并通过代码实现展示了如何检测负权环路,确保正确性和效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载:http://blog.youkuaiyun.com/niushuai666/article/details/6791765

概要

  • Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的。
  • 所以就需要使用其他的算法来求解最短路径,Bellman-Ford算法就是其中最常用的一个,时间复杂度O(VE)。
  • spfa是在Bellman-Ford上的队列优化,时间复杂度O(kE) (k << V),但在最坏的情况下,有可能退化成O(VE)

适用条件和范围

  • 单源最短路径(从源点s到其它所有顶点v)
  • 有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图)
  • 边权可正可负(如有负权回路输出错误提示)
  • 差分约束系统

算法流程

  • 给定图G(V, E)(其中V、E分别为图G的顶点集与边集),源点s,数组dis[i]记录从源点s到顶点i的路径长度,dis数组初始化为INF,dis[s]为0
  • 执行以下操作至多n-1次,n为顶点数

    对于每一条边e(u, v),如果dis[u] + w(u, v) < dis[v],则令dis[v] = dis[u]+w(u, v),w(u, v)为边e(u,v)的权值。
    若上述操作没有对dis数组进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环
    
  • 为了检测图中是否存在负环路,即权值之和小于0的环路。对于每一条边e(u, v),如果存在dis[u] + w(u, v) < dis[v]的边,则图中存在负环路,即是说该图无法求出单源最短路径。否则数组dis[n]中记录的就是源点s到各顶点的最短路径长度。、

Bellman-Ford算法可以大致分为以下三个部分
1. 初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
2. 进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
3. 遍历途中所有的边(edge(u,v)),判断是否存在这样情况:d(v) > d (u) + w(u , v)
存在则返回false,表示途中存在从源点可达的权为负的回路。

代码

#include<iostream>
#include <vector>

using namespace std;
#define INF 0x3fffffff
const int maxn = 1010;

int n , m , s; //点,边,起点
struct Edge {
    int from , to , v;
    Edge() {}
    Edge(int x , int y , int z) {from = x; to = y; v = z;}
};

vector<Edge> edges;
int dis[maxn], pre[maxn];
bool Bellman_Ford()
{
    for(int i = 1; i <= n; i++) //初始化
        dis[i] = (i == s ? 0 : INF);
    for(int i = 1; i < n; i++) for(int j = 0; j < m; ++j) {
        if(dis[edges[j].to] > dis[edges[j].from] + edges[j].v) //松弛(顺序一定不能反~)
        {
            dis[edges[j].to] = dis[edges[j].from] + edges[j].v;
            pre[edges[j].to] = edges[j].from;
        }
    }
    for(int i = 0; i < m; ++i) {
        if(dis[edges[i].from] > dis[edges[i].to] + edges[i].v)
            return false;
    }
    return true;
}

void print_path(int root) //打印最短路的路径(反向)
{
    while(root != pre[root]) //前驱
    {
        cout << root << "-->";
        root = pre[root];
    }
    if(root == pre[root]) cout << root << endl;
}

int main()
{
    cin >> n >> m >> s;
    pre[s] = s;
    for(int i = 0; i < m; i++)
    {
        int from , to , v;
        cin >> from >> to >> v;
        edges.push_back(Edge(from , to , v));
    }
    if(Bellman_Ford()) {
        for(int i = 1; i <= n; i++) //每个点最短路
        {
            cout << dis[i] << endl;
            cout << "Path: ";
            print_path(i);
        }
    }else cout << "have negative circle" << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值