bellman-ford
定义
贝尔曼-福特(Bellman-Ford)算法是一种用于在加权有向图中计算单源最短路径的算法。
运用情况
- 可以处理存在负权边的图。
- 常用于找出图中从一个特定顶点到其他所有顶点的最短路径。
注意事项
- 时间复杂度相对较高。
- 如果图中包含从源点可达的负权循环,则算法无法得出正确结果。
解题思路
它通过对所有的边进行松弛操作,不断更新源点到各个顶点的最短距离估计值。具体步骤如下:
- 初始化所有顶点的距离为无穷大(除了源点距离为 0)。
- 进行多次迭代,对每一条边进行松弛操作。
- 检查是否在迭代后还有边可以被松弛,如果没有,则算法结束,得到最短路径。
例如,有一个图,顶点 A 为源点,边的权值分别为 AB=5,AC=3,BC=-2,通过贝尔曼-福特算法逐步迭代松弛边,可以最终确定从 A 到其他顶点的最短路径。
其他计算最短路径的算法
迪杰斯特拉(Dijkstra)算法:
- 用于在加权有向图或无向图中计算单源最短路径,但不能处理负权边。
- 它通过维护一个顶点的集合,逐步确定到各个顶点的最短距离。
弗洛伊德(Floyd)算法:
- 可以计算任意两点之间的最短路径。
- 通过动态规划的思想,不断更新两点之间经过中间顶点的最短距离。
A*算法:
- 常用于在有启发信息的图中寻找最优路径。
- 结合了贪心算法和迪杰斯特拉算法的一些特点。
例如,在一个交通网络中,可以使用迪杰斯特拉算法找到从一个城市到其他城市的最短驾车路径;在一个电路板布线的问题中,可能会用到弗洛伊德算法来确定各个节点之间的最优布线;而在游戏中的寻路问题中,A*算法常常被采用。
AcWing 853. 有边数限制的最短路
题目描述
853. 有边数限制的最短路 - AcWing题库
运行代码
#include<iostream>
#include<cstring>
using namespace std;
const int N = 510, M = 10010;
struct Edge {int a;int b;int w;
} e[M];//把每个边保存下来即可
int dist[N];
int back[N];//备份数组防止串联
int n, m, k;//k代表最短路径最多包涵k条边
int bellman_ford() {memset(dist, 0x3f, sizeof dist);dist[1] = 0;for (int i = 0; i < k; i++) {//k次循环memcpy(back, dist, sizeof dist);for (int j = 0; j < m; j++) {//遍历所有边int a = e[j].a, b = e[j].b, w = e[j].w;dist[b] = min(dist[b], back[a] + w);//使用backup:避免给a更新后立马更新b, 这样b一次性最短路径就多了两条边出来}}if (dist[n] > 0x3f3f3f3f / 2) return 0x3f3f3f3f;else return dist[n];
}
int main() {scanf("%d%d%d", &n, &m, &k);for (int i = 0; i < m; i++) {int a, b, w;scanf("%d%d%d", &a, &b, &w);e[i] = {a, b, w};}int res = bellman_ford();if (res == 0x3f3f3f3f) puts("impossible");else cout << res;return 0;
}
代码思路
- 引入头文件:
<iostream>
和<cstring>
分别用于输入输出和内存操作(如memset)。 - 命名空间:使用
using namespace std;
来避免频繁地书写std::
前缀。 - 常量定义:
N = 510
和M = 10010
为节点和边的最大数量,用于数组尺寸。 - 结构体Edge:定义边的结构,包含起点
a
、终点b
和权重w
。 - bellman_ford函数:实现最短路径算法的核心逻辑。
- 初始化:用
memset
将所有节点的最短距离初始化为一个很大的数0x3f3f3f3f
,表示无穷大,起点dist[1]
初始化为0。 - 迭代k次:循环k次,每次循环都尝试通过添加一条边来更新路径。
- 备份距离:使用
back
数组备份上一轮的最短距离,以避免路径通过同一个点超过k次,确保不超过k条边的限制。 - 边的遍历:在每次迭代中,遍历所有边,如果通过当前边可以到达一个更短的路径,则更新
dist[b]
。 - 检查负环:若结束时
dist[n]
仍为无穷大,说明没有满足条件的路径,返回0x3f3f3f3f
。
- 初始化:用
-
主函数逻辑
- 读取输入:读取节点数
n
、边数m
和最短路径的最大边数限制k
。 - 构建边集合:读取每条边的信息并存储到结构体数组
e
中。 - 调用bellman_ford:计算从节点1到节点n的满足条件的最短路径长度。
- 输出结果:根据计算结果输出最短路径长度,若为
0x3f3f3f3f
则输出"impossible",表示没有满足条件的路径。
特别说明
- 此代码中,Bellman-Ford算法通常用于检测负权边的最短路径,但在这里做了修改以适应特定限制(最短路径最多包含k条边)。需要注意的是,原始的Bellman-Ford算法通常需要进行n-1次迭代来确保找到所有节点的最短路径,但因为这里加入了额外的限制条件(路径长度上限k),所以迭代次数被限制为k次。
- 由于限制了最短路径的边数,该算法不能保证在存在负权环的情况下仍然正确,这是对传统Bellman-Ford算法应用场景的一种特殊化处理。
改进思路
- 1.路径长度精确控制
- 明确限制条件的处理:当前代码通过迭代
k
次来尝试限制路径中的边数,但这并不能严格保证路径恰好包含k
条边,只是确保不会超过k
条。如果题目要求严格控制边数为k
,则需要在更新距离时,检查当前路径是否刚好为k
条边,若超过则不更新距离。 - 2.优化存储结构
- 邻接表:对于边数较多的情况,使用邻接矩阵(当前隐式使用)可能导致大量空间浪费。可以改为邻接表来存储图,降低空间复杂度,尤其当图稀疏时效果更明显。
- 3.剪枝优化
- 提前终止:在每轮迭代中,如果发现没有距离更新发生,说明已经找到了最优解,可以提前终止循环。
- 起点到终点直接路径:在开始算法之前,检查是否有直接从起点到终点的边,如果有且边数符合限制,这可能是最优解。
- 4.动态规划与状态压缩
- 空间优化:考虑到k较小,可以使用动态规划配合状态压缩技术来减少空间使用。例如,可以用三维数组或滚动数组记录到达每个节点且路径长度为1到k的最短距离。
-
5. 可读性和模块化
- 函数分解:将读取输入、打印输出等功能拆分为单独的函数,提高代码的模块化和可读性。
- 注释完善:在关键步骤和逻辑处理处添加详细注释,便于他人阅读和理解代码意图。
-
6. 错误处理和边界条件检查
- 输入验证:在读取输入时增加合法性检查,比如检查节点编号是否在有效范围内,边的权重是否合理等。
- 输出优化:对于特殊情况,如无可达路径或输入数据非法,可以提供更友好的错误提示信息。
-
7. 使用现代C++特性
- 智能指针和容器:如果涉及动态内存管理,考虑使用智能指针(如
std::unique_ptr
)来自动管理内存。对于数据结构,可以探索使用更高级的STL容器,如std::unordered_map
来加速边的查找。