基础思想和知识
Bellman-ford和SPFA都适用于存在负权边的图中;
Bellman-ford:效率低,较为简单。适用于求有限制边数的最短路。
主要原理:通过迭代k次/遍历操作,反复对每条边进行松弛操作,求经过不超过k条边的走到每个最短路的操作。
基本步骤:
Bellman-ford:效率低,较为简单。适用于求有限制边数的最短路。
主要原理:通过迭代k次操作,反复对每条边进行松弛操作,求经过不超过k条边的走到每个最短路的操作。
松弛操作:dist[b]=min(dist[b],backup[a]+w);
backup数组:作为备份数组,防止串联更新导致的第k层循环后找到一个边数大于k的路径;满足“第i次更新的最短路径的最大边数是i”
基本步骤:
for(int i=1;i<=k;i++) // 第i次更新的最短路径的最大边数是i
memcpy(backup,dist,sizeof(dist)); // 备份数组
for(int j=0;j<m;j++) // 进行松弛操作
更详细的解释建议仔细看视频
SPFA为Bellman-ford的队列优化,常用于处理存在负权边的图,例如负环等。
优势:解决Bellman-ford迭代/遍历更新所有边产生的时间复杂。
主要原理:使用队列的数组结构,每次只放入距离被更新了的节点,遍历与之相连的边判断是否能更新最短距离。使用st[i]数组,标记i点是否在队列中,若i点被更新且不在队列中则放入队列。
题目
Bellman-ford求有变数限制的最短路
853. 有边数限制的最短路
题意:求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出impossible
。
题解:
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int dist[N],backup[N];
struct node{
int a,b,w;
}edges[N];
int n,m,k;
void bellman_ford()
{
memset(dist,0x3f, sizeof(dist));
dist[1]=0;
for(int i=0;i<k;i++)//第i次更新的最短路径边数是i
{
memcpy(backup,dist,sizeof(dist));
//备份数组,防止串联更新,导致第k层循环后找到一个边数大于k的路径
for(int j=0;j<m;j++)//松弛操作
{
int a=edges[j].a,b=edges[j].b,w=edges[j].w;
dist[b]=min(dist[b],backup[a]+w);
}
}
}
int main()
{
cin>>n>>m>>k;
for(int i=0;i<m;i++)
{
int a,b,w;
cin>>a>>b>>w;
edges[i].a=a;
edges[i].b=b;
edges[i].w=w;
}
bellman_ford();
if(dist[n]> 0x3f3f3f3f/2 ) cout<<"impossible";
/*
0x3f是63,0x3f3f3f3f是1061109567,都是16进制。
memset函数是按照字节赋值
由于dist数组是int型,每个数据为4个字节
每个字节赋值为0x3f,所以int型赋值就是0x3f3f3f3f。
*/
else cout<<dist[n];
return 0;
}
SPFA判断负环
判断负环基本思想:在循环更新了n-1次后,若最短路径长度还继续更新,则一定存在负环。
额外使用cnt数组,cnt[x] == n表示经过了n条边,至少经过了n+1个点
题目1
题解:这道题卡了七八发,问题在于没注意到题目是求从1出发的负环,我写成了从任意点出发的负环。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N=2e4+10;
int h[N],e[N],ne[N],idx,w[N];
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
bool vis[N];
int dis[N],cnt[N];
int main()
{
int t;
cin>>t;
while(t--)
{
int flag=0;
memset(h,-1,sizeof h);
idx=0;
memset(cnt,0,sizeof cnt);
memset(vis,0,sizeof vis);
memset(dis,inf,sizeof dis);
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
if(c>=0) add(b,a,c);
}
queue<int> q;
vis[1]=true;//主要是这里开始的一下三行
q.push(1);//定义从第1个点出发
dis[1]=0;
/*
//如果是从任一点出发的话,上面三行应该改为
for(int i=1;i<=n;i++)
{
vis[i]=true;
q.push(i);
}
*/
while(q.size())
{
int t=q.front();
q.pop();
vis[t]=false;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(dis[j]>dis[t]+w[i])
{
dis[j]=dis[t]+w[i];
cnt[j]=cnt[t]+1;
/*
cnt当前最短路边数
cnt[x]==n 表示经过了n条边,至少经过了n+1个点
则一定经过了环,这个环一定是负环(因为如果这个环不是负环的话,他是不会经过这个环的)
所以是应该有“=”的
*/
if(cnt[j]>=n)
{
flag=1;
break;
}
if(!vis[j])
{
q.push(j);
vis[j]=true;
}
}
}
}
if(flag==1) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
题目2
题解:这道题和上面这道很像,区别只是在于只有一组图,寻找的是任意点出发的环。
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int dis[N];
bool st[N];
int n,m;
int h[N],e[N],ne[N],w[N],idx;
int cnt[N];
bool spfa()
{
memset(dis,0x3f,sizeof dis);
queue<int>q;
for(int i=1;i<=n;i++)
{
q.push(i);
st[i]=true;
}
//放入所有点,因为负环可能不能从1号点得到,而能从其他点得到
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(dis[j]>dis[t]+w[i])
{
dis[j]=dis[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n)
{
return true;
}
if(!st[j])
{
q.push(j);
st[j]=true;
}
}
}
}
return false;
}
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
if(spfa()) cout<<"Yes";
else cout<<"No";
}
SPFA求最短路
原题链接
非常基础的求最短路题目,存在负权边,所以不适用于diji,且数据量大,用spfa是较好的选择。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m;
int h[N],e[N],ne[N],idx,w[N];
bool st[N];
queue<int>q;
int t;
int dis[N];
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
int spfa()
{
memset(dis,0x3f,sizeof dis);
dis[1]=0;
q.push(1);
st[1]=true;
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(dis[j]>dis[t]+w[i])
{
dis[j]=dis[t]+w[i];
if(!st[j])
{
q.push(j);
st[j]=true;
}
}
}
}
/*if(dis[n]==0x3f3f3f3f) return -1;
else return dis[n];*/
//如果像上面这样写,dis[n]=-1是造成输出错误
return dis[n];
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++)
{
int u,v,k;
cin>>u>>v>>k;
add(u,v,k);
}
t=spfa();
if(t==0x3f3f3f3f) cout<<"impossible";
else cout<<t;
return 0;
}
总结
还处于入门中,像盲人摸象一样在摸找门槛,继续加油!