任何遗漏、错误、疑问、建议,请在评论区指出哦~
希望这篇文章可以帮助到你,谢谢你阅读它
Floyed 算法
Floyed-Warshall 算法,简称 Floyed,是一种基于动态规划思想的多源最短路算法,其中“多源”是指,可以计算从多个结点为起点出发的情况。
缺陷:时间复杂度为 。
step1:定义
定义 dist[k][i][j] 为,从 出发,经过
到结点
的最短路径长度。
step2:状态转移
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
代码实现
对于板题:洛谷 B3647 【模板】Floyd
#include<bits/stdc++.h>
using namespace std;
int n,m;
int dis[103][103];
const int INF=0x3f3f3f3f;
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
if(i==j) dis[i][j]=0;
else dis[i][j]=INF;//初始化为无穷大
}
}
int x,y,w;
for(int i=0;i<m;i++) {
scanf("%d%d%d",&x,&y,&w);
dis[y][x]=dis[x][y]=min(dis[x][y],w);//将路径长度初始化为边权
}
for(int k=1;k<=n;++k)
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(i!=j && i!=k && j!=k)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);//更新最短路长度
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++)
printf("%d ",dis[i][j]);//输出从i到j的最短路径长度
cout<<'\n';
}
return 0;
}
Dijkstra 算法
Dijkstra 算法,是一种经典的,基于贪心的单源最短路径算法。适用于非负权图。
step1:核心步骤
-
维护两个集合:已确定最短路径的顶点集合
和未确定的集合
-
每次从
中选择距离起点最近的顶点加入
-
更新该顶点所有邻接点的距离
-
重复直到所有顶点都在
中
step2:重要定义
【dis 数组】
vector<int> dis(n, INF); // 从源点到各顶点的最短距离,初始化为无穷大
dis[i] 表示从原点到编号为 的点,的目前已知距离。
这个值随着算法不断更新。
【vis 数组】
vector<bool> vis(n, false); // 标记顶点是否已确定最短路径
对应算法设计中的集合 ,值为 true 则代表最短路已经确定。
代码实现
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll INF = 1e18; // 大于最大可能的总权重
int main() {
int n, m, s;
cin >> n >> m >> s;
// 邻接表,使用1-based索引
vector<vector<pair<int, ll> > > adj(n + 1);
// 读入边
for (int i = 0; i < m; i++) {
int u, v;
ll w;
cin >> u >> v >> w;
adj[u].emplace_back(v, w); // 有向边
}
// 初始化距离数组
vector<ll> dis(n + 1, INF);
dis[s] = 0;
// 标记已确定最短路径的顶点
vector<bool> vis(n + 1, false);
// 优先队列(最小堆),存储 (距离, 顶点)
priority_queue<pair<ll, int>,
vector<pair<ll, int>>,
greater<pair<ll, int>>> pq;
pq.push({0, s});
// Dijkstra主循环
while (!pq.empty()) {
// 取出当前距离最小的顶点
auto [d, u] = pq.top();
pq.pop();
// 如果已经处理过,跳过
if (vis[u]) continue;
// 标记为已处理
vis[u] = true;
// 遍历所有邻接点
for (const auto& [v, w] : adj[u]) {
// 松弛操作
if (!vis[v] && dis[u] + w < dis[v]) {
dis[v] = dis[u] + w;
pq.push({dis[v], v});
}
}
}
// 输出结果
for (int i = 1; i <= n; i++) {
cout << dist[i] << (i == n ? "\n" : " ");
}
return 0;
}
Bellman-Ford
Bellman-Ford算法用于计算带权有向图中单源最短路径,基于动态规划思想,能够处理负权边,并能检测负权环的存在。
step1:核心思想
-
动态规划思想:通过多次松弛操作逐步逼近最短路径
-
松弛操作:对每条边进行
dis[v] = min(dis[v], dis[u] + w) -
需要进行n-1 轮松弛(n为顶点数),确保最短路径找到
-
第n轮检查是否还能松弛,以此判断是否存在负权环
step2:对比 Dijkstra
| 特性 | Dijkstra | Bellman-Ford |
|---|---|---|
| 权重限制 | 非负权重 | 任意权重 |
| 时间复杂度 | O((V+E)logV) | O(VE) |
| 空间复杂度 | O(V+E) | O(V) |
| 能否检测负环 | 否 | 能 |
| 适用场景 | 正权图快速求解 | 负权图、有边数限制 |
(权威.jpg)
step3:重要定义
【dis 数组】
vector<int> dis(n, INF); // 从源点到各顶点的最短距离
-
初始:
dist[src] = 0,其他为INF
重要部分代码
松弛操作:
for (int i = 0; i < n - 1; i++) {
bool upd = false;//检查是否更新
// 遍历所有边
for (const auto& [u, v, w] : edges) {
if (dist[u] != INF && dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
parent[v] = u; // 更新前驱
upd = true;
}
}
// 如果没有更新,提前结束(优化)
if (!upd) break;
}
第 n 轮检查负环:
bool has = false;
for (const auto& [u, v, w] : edges) {
if (dist[u] != INF && dist[u] + w < dist[v]) {
has = true;
break;
}
}
板题示例:
洛谷 P4779 【模板】单源最短路径(标准版),Bellman做法
#include <iostream>
#include <vector>
#include <tuple>
using namespace std;
typedef long long ll;
const ll INF = 1e18;
int main() {
int n, m, s;
cin >> n >> m >> s;
vector<tuple<int, int, ll> > edges(m);
for (int i = 0; i < m; i++) {
int u, v;
ll w;
cin >> u >> v >> w;
edges[i] = {u, v, w};
}
vector<ll> dist(n + 1, INF);
dist[s] = 0;
// Bellman-Ford主循环(n-1轮)
for (int i = 1; i < n; i++) { // 最多n-1轮
bool upd = false;
for (const auto& [u, v, w] : edges) {
if (dist[u] != INF && dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
upd = true;
}
}
if (!upd) break; // 提前终止
}
for (int i = 1; i <= n; i++) {
cout << dist[i] << (i == n ? "\n" : " ");
}
return 0;
}
SPFA 算法
著名的已死算法。
SPFA 是 Bellman-Ford 的队列优化版本,它通过队列动态管理待松弛的顶点,避免了Bellman-Ford中不必要的松弛操作,效率更高。
step1:核心思想
-
动态松弛:只对距离发生变化的顶点进行松弛操作
-
队列管理:使用队列保存待处理的顶点
-
避免重复:同一顶点不会在队列中重复出现(除非距离再次更新)
-
负环检测:通过顶点入队次数检测负权环
step2:对比 Bellman-Ford
| 特性 | Bellman-Ford | SPFA |
|---|---|---|
| 松弛方式 | 每轮松弛所有边 | 只松弛与队列中顶点相关的边 |
| 数据结构 | 无特殊要求 | 队列 |
| 平均时间复杂度 | O(VE) | O(kE), k≈2(实践中) |
| 最坏时间复杂度 | O(VE) | O(VE)(与BF相同) |
| 空间复杂度 | O(V+E) | O(V+E) |
| 负环检测 | 第V轮松弛 | 入队次数超过V次 |
主循环代码
while (!q.empty()) {
int u = q.front();
q.pop();
inQ[u] = false; // 出队标记
// 松弛所有邻接边
for (auto [v, w] : adj[u]) {
if (dis[u] + w < dis[v]) {
dis[v] = dis[u] + w;
// 如果v不在队列中,加入队列
if (!inQ[v]) {
q.push(v);
inQ[v] = true;
count[v]++;
// 负环检测:入队次数超过n次
if (count[v] > n) {
// 存在负环
return true;
}
}
}
}
}
写在最后
完结撒花,总结:
| 算法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| Dijkstra (优先队列) | O((V+E)logV) | O(V+E) | 无负权边的单源最短路 |
| Bellman-Ford | O(VE) | O(V+E) | 可处理负权边,检测负环 |
| SPFA | O(VE) ~ O(E) | O(V+E) | 稀疏图的负权边处理 |
| Floyd-Warshall | O(V³) | O(V²) | 多源最短路,稠密图 |
有问题请写在评论区,谢谢。
1897

被折叠的 条评论
为什么被折叠?



