Bellman_ford算法
适用条件
- 单源最短路径
- 存在负权值边
- 检测负权回路
核心操作
- 松弛:对每条边与源点的距离重新计算
cpp
if (dist[item.v1] != INT_MAX && dist[item.v1] + item.w < dist[item.v2]) {
dist[item.v2] = dist[item.v1] + item.w;//dist[item.v1]未更新时应该跳过
}
- 迭代:可以视为一种逼近 ,利用边的信息一层层迭代,每一层都利用了上一层迭代的结果,所以源点到达各个结点的距离会越来越接近最小值,最终达到终点的距离也会接近最小值(但这好像不是贪心的思想,而只是简单的迭代逼近)
个人代码
cpp
using namespace std;
using ll = long long;
int n, m, s, t, v;
struct Edge {
int v1, v2, w;
};
void solve() {
cin >> n >> m;
vector<Edge>edges;
vector<int>dist(n + 1, INT_MAX); dist[1] = 0;
while (m--) {
cin >> s >> t >> v;
edges.push_back({ s,t,v });
}
for (int i = 1; i < n; i++) {
for (auto item : edges) {
if (dist[item.v1] != INT_MAX && dist[item.v1] + item.w < dist[item.v2]) {
dist[item.v2] = dist[item.v1] + item.w;//dist[item.v1]未更新时应该跳过
}
}
}
if (dist[n] == INT_MAX) {
cout << "unconnected";
}
else {
cout << dist[n];
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0); std::cout.tie(0);
solve();
return 0;
}
判断负权回路
输出结果前多加上一层循环,改掉判断逻辑即可
cpp
for (auto item : edges) {
if (dist[item.v1] != INT_MAX && dist[item.v1] + item.w < dist[item.v2]) {
cout << "circle";
return;//return不可以省略
}
}
注意事项
dist[item.v1] != INT_MAX
不可以省略vector<Edge>edges;
存储方式既不是邻接矩阵也不是邻接表
优化版SPFA
核心操作
- 队列操作:类似BFS,目的是防止对一个出更新的边进行无意义的操作
- 存储方式:邻接表
个人代码
cpp
using namespace std;
using ll = long long;
int n, m, s, t, v;
struct Edge {
int vex, weight;
};
void solve() {
cin >> n >> m;
vector<int>dist(n + 1, INT_MAX); dist[1] = 0;
vector<list<Edge>>grid(n + 1, list<Edge>());
queue<Edge>q;
while (m--) {
cin >> s >> t >> v;
grid[s].push_back({ t,v});
}
q.push({ 1,0 });
while (!q.empty()) {
Edge cur = q.front();
q.pop();
for (auto item : grid[cur.vex]) {
if (dist[cur.vex] + item.weight < dist[item.vex]) {
dist[item.vex] = dist[cur.vex] + item.weight;
q.push(item);
}
}
}
if (dist[n] == INT_MAX) {
cout << "unconnected";
}
else {
cout << dist[n];
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0); std::cout.tie(0);
solve();
return 0;
}
判断负权回路
只要一个顶点被加入到队列的次数>=n,一定出现了负权回路
- 定义计数数组
cpp
vector<int>count(n + 1, 0);
- 初始化队列
cpp
q.push({ 1,0 }); count[1]++;
- 队列操作逻辑更新
cpp
while (!q.empty()) {
Edge cur = q.front();
q.pop();
if (count[cur.vex] >= n) {//判断加入队列的次数
cout << "circle";
return;
}
for (auto item : edges[cur.vex]) {
if (dist[cur.vex] + item.weight < dist[item.vex]) {
dist[item.vex] = dist[cur.vex] + item.weight;
q.push(item);
count[item.vex]++;
}
}
}
时间复杂度
- 朴素版Bellman_ford算法:O(NM),N是顶点数,M是边数
- SPFA算法:O(KN),N是顶点数,K是个不定值,取决于总共加入多少条边到队列中去
- 进出队列的时间不计