当前位置: 首页 > ai >正文

图论——Bellman-Ford和SPFA

Bellman-Ford & SPFA

这两者实现的操作是一样的,前者比较能反应该算法的原理,较为直观。而后者则是它的优化版本。

先贴一道模板题:P3385 【模板】负环 - 洛谷

Bellman-Ford

算法介绍

Bellman-Ford算法的应用场景是什么呢?在前面的Djikstra算法中,我们用它处理单源点到图中所有点的最小距离,前提是所有边权皆为正。但是当一些问题中边权为负的时候就会出错。下面用一张图表简单看一下:

核心维度Dijkstra 算法Bellman-Ford 算法
边权限制仅支持非负权边(有负权边时结果错误)支持负权边(无限制)
负环处理无法检测负环,甚至可能因负权边输出错误结果能检测从起点可达的负环(N-1 轮后仍可松弛)
时间效率高(优化版 O (M log N))低(朴素版 O (N*M))
核心思想贪心:每次确定 “当前最短顶点” 的路径,不再更新动态规划:通过 N-1 轮松弛所有边,逐步逼近最短路径

接下来详细解释一下两者的功能区别:

          [1]  (起点,方框代表顶点)/   \3 /     \  5/       \[2] ←─────── [3]  (终点)-3

假设有这么一张图,现在我要求1->2的最短距离。用Djikstra算法的思想:很明显,因为边权5>3,所以会选择直接从1->2而不会选择先到3再到2。因为此算法是局部的贪心,并不会考虑到负边权的情况。可以看到,事实是如果再从3->2的话边权会更小。

其实Bellman-Ford就是将所有能遍历的点都给遍历一遍,做一个“全局贪心”。那么代价就是由于它并不会提前结束遍历,时间复杂度会高于Djikstra.所以在解决没有负边权的时候优先使用Djikstra算法。

代码解释

void bellmanFord(int n) 
{vector<int> dis(n+1, INF);dis[1] = 0;for(int i=1; i<n; ++i) // 进行n-1次松弛操作 {bool updated = false;for (int j = 0; j < edge.size(); ++j) {int u = edge[j].u;int v = edge[j].v;int w = edge[j].w;// 如果可以松弛,就更新距离if (dis[u] != INF && dis[v] > dis[u] + w) {dis[v] = dis[u] + w;updated = true;}}if(!updated) break;  // 没有更新,提前结束}// 检测负环:如果还能松弛,说明存在负环for(int j = 0; j < edge.size(); ++j){int u = edge[j].u;int v = edge[j].v;int w = edge[j].w;if(dis[u] != INF && dis[v] > dis[u] + w) // 存在负环{cout << "存在负环" << endl;return ;}}for(int i=1; i<=n; i++) cout << dis[i] <<' ';//输出起点到图上其他点的最短距离cout << endl;
}
void solve() 
{int n, m;cin >> n >> m;for(int i = 0; i < m; ++i) {int u,v,w;cin >> u >> v >> w;edge.push_back({u,v,w});}bellmanFord(n);
}

SPFA(Shortest Path Fastest Algorithm)

通过名字就可以感受到这个算法又短又快,其实它是Bellman-Ford的优化版本,其大致原理是一致的。

  • 队列优化:本算法仅将可能松弛的点放到队列里面,避免对每个点都进行遍历的冗余操作。
  • 负环检测:记录每个顶点的入队次数,若某个顶点入队次数超过 n(顶点总数),则说明存在从起点可达的负环(因为正常最短路径最多经过 n 个顶点,入队次数不会超过 n)。

代码解释

int dis[N],cnt[N];
struct node  {int v; int w;
};
vector<node> g[N];
queue<int> q;
int x,y,z;
bool SPFA(int x)
{dis[x] = 0;q.push(x);while(q.size()){int u = q.front();q.pop();for(auto i:g[u]){int nv = i.v;int nw = i.w;if(dis[nv] > dis[u] + nw){dis[nv] = dis[u] + nw;if(++cnt[nv] >= n) return 1; //是负环q.push(nv);}}}return 0;
}
void solve()
{memset(dis,0x3f,sizeof(dis));memset(cnt,0,sizeof(cnt));for(int i=1;i<=n;i++) g[i].clear(); //初始化邻接表cin >> n >> m;for(int i=1;i<=m;i++){cin >> x >> y >> z;g[x].push_back({y,z});if(z >= 0) g[y].push_back({x,z});//存图 只有正数才存双向边}if(SPFA(1)) cout << "YES" << endl;else cout << "NO" << endl;
}
http://www.xdnf.cn/news/18127.html

相关文章:

  • 大模型+RPA:如何用AI实现企业流程自动化的“降本增效”?
  • traceroute命令使用指南
  • Linux学习-5网络管理
  • 企业如何让内部视频仅限指定域名播放,确保视频不被泄露?
  • SpreadJS 协同服务器 MongoDB 数据库适配支持
  • Flink Checkpoint 原理深度剖析与作用讲解(flink面试高频问题)
  • RK3128增加usb调试模式,开放adb和root权限
  • 分布式搜索(Elasticsearch)深入用法
  • 基于Python的宠物服务管理系统 Python+Django+Vue.js
  • 卫生许可证识别技术:通过OCR与NLP实现高效合规管理,提升审核准确性与效率
  • 传输层协议——UDP和TCP
  • 论文阅读:Prompt Optimization in Large Language Models
  • 传统概率信息检索模型:理论基础、演进与局限
  • 轻度娱乐浪潮下定制开发开源AI智能名片S2B2C商城小程序的机遇与策略
  • RNN(循环神经网络)和Transformer是处理自然语言处理(NLP)任务区别
  • 10.Ansible角色管理
  • 力扣2道dp
  • Rust 入门 生命周期-next2 (十九)
  • flask——4:请求与响应
  • Kubernetes(K8s)常用命令全解析:从基础到进阶
  • Unity进阶--C#补充知识点--【Unity跨平台的原理】Mono与IL2CPP
  • Disbursement on Quarantine Policy(概率、逆元计算期望)
  • 【深度学习】pytorch深度学习框架的环境配置
  • Ansible文件部署与大项目多主机管理
  • 学习嵌入式的第二十天——数据结构
  • redis-集成prometheus监控(k8s)
  • 实习两个月总结
  • 从0到1掌握 Spring Security(第三篇):三种认证方式,按配置一键切换
  • 传统方式部署(RuoYi-Cloud)微服务
  • 像素风球球大作战 HTML 游戏