专题二
bellman-ford算法适用于存在负权边的稀疏图(有最短边数限制)。
时间复杂度:O(nm)。
bellman-ford算法的步骤如下:
(1)把所有点到源点的距离初始化为∞,把源点到源点的距离初始化为0;
原因:以防有部分点没有连接在大的图上,初始化一下。
(2)遍历 最短路径数量的 次数 去遍历所有的边,更新所有的边用每一条边去更新各点到源点的距离 。
原因:限制了最短路径,那么每一次只遍历和节点相连的点,更新边长。
细节问题:
(1) 需要把dist数组进行一个备份,这样防止每次更新的时候出现串联;
串联的原因:在遍历过程中那些改变状态的点都是与初始状态相连的点,但是在遍历完
了这些点后,其他点会因为这些点的改变而改变,所以我们需要记录状态,进行备份。
(2) 由于存在负权边,因此return -1的条件就要改成dist[n]>0x3f3f3f3f/2;
原因:因为有边数的限制所以应该不会是负无穷,但是还是减了,但是又不会太小,所以
判断为0x3f3f3f3f/2;
(3) 上面所谓的n次遍历的实际含义是当前的最短路径最多有n-1条边,这也就
解释了为啥要i遍历到n的时候退出循环了,因为只有n个点,最短路径无环最多
就存在n-1条边。
(4) 这里无需对重边和自环做单独的处理。
[1] 重边:由于遍历了所有的边,会遍历到较短的那一条;
[2] 自环:有边数的限制,所以不会死循环;
(5)每次备份记得还原,然后进行下一步操作。
代码如下:
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=510,M=1000010;
struct edg {
int a,b,w;
} e[M];
int dist[N],back[N];
int n,m,c;
int bellman() {
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0; i<c; i++) {
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);
}
}
if(dist[n]>0x3f3f3f/2)return -1;
return dist[n];
}
int main() {
scanf("%d%d%d",&n,&m,&c);
for(int i=0; i<m; i++) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
e[i]= {
x,y,z};
}
int t=bellman();
if(t==-1)puts("impossible");
else cout<<t<<endl;
return 0;
}
spfa算法:
在bellman-man算法的改进版:
看内容(2),每一次都要遍历所有的点,但是实际上只要遍历与改变点相连的点即可。
那么我们可以用队列来进行优化。
时间复杂度由之前的O(nm)到最大的O(nm)最小的O(n)。
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=1e5+10;
#define fi first
#define se second
typedef pair<int,int> PII;//到源点的距离,下标号
int h[N],e[N],w[N],ne[N],idx=0;
int dist[N];//各点到源点的距离
bool st[N];
int n,m;
void add(int a,int b,int c) {
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
int spfa() {
queue<PII> q;
memset(dist,0x3f,sizeof dist);
dist[1]=0;
q.push({
0,1});
st[1]=true;
while(q.size()) {
PII p=q.front();
q.pop();
int t=p.se;
st[t]=false;//从队列中取出来之后该节点st被标记为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];
if(!st[j]) {
//当前已经加入队列的结点,无需再次加入队列,即便发生了更新也只用更新数值即可,重复添加降低效率
st[j]=true;
q.push({
dist[j],j});
}
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
else return dist[n];
}
int main() {
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--) {
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
int res=spfa();
if(res==-1) puts("impossible");
else printf("%d",res);
return 0;
}
注意:
利用bellman-ford算法后
图中每一条边都满足:
a------->b权值为w
dist [b] <= dist [a] + w.
称为三角不等式
bellman-man算法适用于存在负权边的稀疏图(有最短路径限制)
而spfa适用于大部分图的问题。