图论算法之最短路问题
Bellman-Ford算法
思路
所有的边进行n-1轮松弛,因为在一个含有n个顶点的图中,任意两点之间的最短路径最多包含n-1条边。换句话说,第1轮在对所有的边进行松弛操作后,得到从1号顶点只能经过一条边到达其余各定点的最短路径长度,第2轮在对所有的边进行松弛操作后,得到从1号顶点只能经过两条边到达其余各定点的最短路径长度,…
特点
单源最短路算法,可求含有负权边的最短路,Bellman-Ford算法可以检测一个图是否含有负权回路:如果经过n-1轮松弛后任然存在dist[e[i]]>dist[s[i]]+w[i],可求经过x条边的最短路距离
例题
代码
#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 -1;
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 == -1) puts("impossible");
else cout << res;
return 0;
}
spfa算法
特点
SPFA(Shortest Path Faster Algorithm)算法是求单源最短路径的一种算法,它是Bellman-ford队列优化,它是一种十分高效的最短路算法。SPFA算法可以判断图中是否有负权环,即一个点的入队次数超过N
思路
建立一个队列,初始时队列里只有起始点s,在建立一个数组记录起始点s到所有点的最短路径(初始值都要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里的点去刷新起始点s到所有点的距离的距离,如果刷新成功且刷新的点不在队列中,则把该点加入到队列,重复执行直到队列为空。
例题
代码
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=1e5+10,M=2*N;
int n,m;
// 邻接表的数据结构
int h[N],e[M],ne[M],w[M],idx;
void add(int a,int b,int c) {
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
int dist[N];
int cnt[N];
// 存储某一个点是否在队列中,存重复的点没有意义
bool st[N];
bool spfa() {
// 只判断是否存在负环时不需要进行初始化
// memset(dist,0x3f,sizeof dist);
// dist[1]=0;
queue<int> q;
// q.push(1);
// st[1]=true;
// 把所有点全部放到队列中,因为负环可能从任何点伸出
for(int i=1;i<=n;i++) {
st[i]=true;
q.push(i);
}
while(q.size()) {
int t = q.front();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i]) {
int j=e[i];
if(dist[j]>dist[t]+w[i]) {
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n) return true;
if(!st[j]) {
q.push(j);
st[j]=true;
}
}
}
}
// 只判断是否存在负环时不需要以下这句
// if(dist[n] == 0x3f3f3f3f) return -1;
return false;
}
int main() {
cin>>n>>m;
memset(h,-1,sizeof(h));
for(int i=0;i<m;i++) {
int a,b,c; cin>>a>>b>>c;
add(a,b,c);
}
if(spfa()) puts("Yes");
else puts("No");
return 0;
}
Floyd算法
特点
多源最短路算法
思路
三重循环,最外层枚举每个点最为中间节点,内两层枚举每种点对并以k点为中间节点进行距离的更新。实际上是利用了DP的思想
例题
代码
#include<iostream>
using namespace std;
const int N=210,INF=0x3f3f3f3f;
int n,m,Q;
int d[N][N];
void floyd() {
for(int k=1;k<=n;k++) {
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
d[i][j]=min(d[i][j], d[i][k]+d[k][j]);
}
}
}
}
int main() {
cin>>n>>m>>Q;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i==j) d[i][j]=0;
else d[i][j]=INF;
for(int i=0;i<m;i++) {
int a,b,w; cin>>a>>b>>w;
// 处理重边
d[a][b]=min(d[a][b],w);
}
floyd();
while(Q --) {
int a,b; cin>>a>>b;
if(d[a][b]>INF/2) puts("impossible");
else cout<<d[a][b]<<endl;
}
return 0;
}