Bellman-Ford(贝尔曼-福特) & SPFA算法
Bellman-Ford
一种基于松弛(relax)操作——广度,的最短路算法。
优点
可以有负权边,但是不能出现负权环
相对于迪杰特斯拉算法而言,它的时间复杂度更高,但是能解决负权边问题
检测图中是否有负权值回路
负权边:权重为负数的边。
负权环:源点到源点的一个环,环上权重和为负数。
判断是否存在负权环
//解法:
/*
跑Bellman-Ford算法
如果有点被松弛成功了n次,那一定存在
如果n-1次之内算法就结束了,那一定不存在
*/
//判断是否存在负环
for(int i=1;i<=cnt;i++){
if(dis[e[i].u]==inf || dis[e[i].v]==inf)
continue;
//在经过第一遍松弛之后,应该得到最小路径,若还能继续松弛,说明存在负权环
if(dis[e[i].v]>dis[e[i].u]+e[i].w)
return true;
}
return false;
邻接表实现
#include <bits/stdc++.h>
using namespace std;
const int N=2e6+10,inf=1e9+7;
struct node{
int u;
int v;//终点
int w;//权值
}e[N];
int n;//点
int m;
//不一定是边数
//当负权值边时,只有一条边;
//当正权值边时,存在两条边(无向图)
int cnt=0;
int dis[N];
bool bellman_ford(){
dis[1]=0;//从顶点1出发,
//dis[s]=0;从顶点s出发
for(int i = 2; i <= n; i++)
dis[i] = inf;//除出发点的其余点的移动距离均为inf
for(int i=1;i<=n-1;i++)//到n-1个点,因为最后一个点是终点
for(int j=1;j<=cnt;j++)//cnt是真正的边数
//松弛函数
//dis[e[j].v] > dis[e[j].u]+e[j].w
// 终点 起点 当前点的权值
if(dis[e[j].u]!=inf && dis[e[j].v] > dis[e[j].u]+e[j].w)
dis[e[j].v] = dis[e[j].u]+e[j].w;
//判断是否存在负环
for(int i=1;i<=cnt;i++){
if(dis[e[i].u]==inf || dis[e[i].v]==inf)
continue;
//在经过第一遍松弛之后,应该得到最小路径,若还能继续松弛,说明存在负权环
if(dis[e[i].v]>dis[e[i].u]+e[i].w)
return true;
}
return false;
}
int main()
{
int t;
cin>>t;
while(t--){
cnt=0;
memset(e,0,sizeof(e));
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
if(w<0)
e[++cnt]={u,v,w};
if(w>=0)
e[++cnt]={v,u,w},e[++cnt]={u,v,w};;
}
if(bellman_ford())
cout<<"YES"<<endl;//存在负权环
else
cout<<"NO"<<endl;
//dis[n] 从顶点1出发得到的最短路径
//dis[e] 从顶点s出发得到的最短路径
}
return 0;
}
SPFA
思想: 逼近 + 松弛 + bfs
操作
- 将源点压入队列
- 取出队头元素,对该元素的各邻接边进行松弛操作。如果该点没有在队列中,就压入队列
- 重复步骤 1.
判断是否存在负权环
-
首先将1~n个顶点都入队,因为题目不是说从顶点1开始到顶点n查找负权环,而是在每个顶点都可能存在负权环。
-
定义一个cnt[x]数组来存储每个顶点
int cnt[N]; //每个顶点的边数
-
cnt[x]:1~x出现的边数,如果出现的边数==n了,就说明到x这个顶点出现了n+1个顶点,在这n个顶点必定有两个顶点是相同的,而我们每次判断负权环是在能够压缩路径的前提下(也就是有更短距离,距离变小的情况下才压缩、判断,所以只会是更小的负权才压缩)这样就存在了一个负权环。
if(dist[v]>dist[u]+w)
{
dist[v]=dist[u]+w;
cnt[v]=cnt[u]+1; //对出边顶点的边数+1
if(cnt[v]>=n){//如果1~v这个顶点的边数==n,说明有n+1个顶点
flag=0;
break;
}
}
#include <bits/stdc++.h>
using namespace std;
const int N=2e6+10,inf=1e9+7;
struct node{
int v;//终点
int w;//权值
int next;
}edge[N];
queue<int> q;
int n,m;//点,边
int head[N];
int book[N];//标记是否入队
int dis[N];//记录移动
int cnt[N];//每个顶点的边数
int pos=0;
int flag=1;
void add_edge(int u,int v,int w){
edge[pos].v = v;
edge[pos].w = w;
edge[pos].next = head[u];
head[u] = pos++;
}
void spfa(){
memset(dis,inf,sizeof(dis));//没去过的点统统标记为无穷
for(int i=1;i<=n;i++){
q.push(i);//所有点入队
book[i]=1;//入队标记
}
while(!q.empty()){
int t = q.front();//弹出
q.pop();
book[t] = 0;//出队取消标记
for(int i=head[t];i!=-1;i=edge[i].next)
//遍历这个点的所有边
{
int to = edge[i].v;
int weight = edge[i].w;
//松弛操作,进行压缩
if(dis[to]>dis[t]+weight){
dis[to] = dis[t]+weight;
cnt[to] = cnt[t] + 1;//对该边终点的边数+1
if(cnt[to]>=n){ //如果1~v这个顶点的边数==n,说明有n+1个顶点
flag = 0;
break;
}
//压缩后,如果在队外,进行入队
if(!book[to])
{
q.push(to);
book[to]=1;
}
}
}
if(!flag)
break;
}
}
int main()
{
cin>>n>>m;
memset(head,-1,sizeof(head));
//memset(dis,inf,sizeof(dis));
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
add_edge(u,v,w);
}
spfa();
if(!flag)
cout <<"Yes";
else
cout <<"No";
return 0;
}
求最短路(链式前向星)
类似于bfs的广度搜索
#include <bits/stdc++.h>
using namespace std;
const int N=2e6+10,inf=1e9+7;
struct node{
int v;//终点
int w;//权值
int next;
}edge[N];
queue<int> q;
int n,m;//点,边
int head[N];
int book[N];//标记是否入队
int dis[N];//记录移动
int cnt=0;
void add_edge(int u,int v,int w){
edge[cnt].v = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt++;
}
void spfa(){
memset(dis,inf,sizeof(dis));//没去过的点统统标记为无穷
dis[1] = 0;//起始点为0
q.push(1);//起始点入队
book[1] = 1;//标记入队
while(!q.empty()){
int t = q.front();//弹出
q.pop();
book[t] = 0;//出队取消标记
for(int i=head[t];i!=-1;i=edge[i].next)
//遍历这个点的所有边
{
int to = edge[i].v;
int weight = edge[i].w;
//松弛操作,进行压缩
if(dis[to]>dis[t]+edge[i].w){
dis[to] = dis[t]+edge[i].w;
//压缩后,如果在队外,进行入队
if(!book[to])
{
q.push(to);
book[to]=1;
}
}
}
}
}
int main()
{
cin>>n>>m;
memset(head,-1,sizeof(head));
//memset(dis,inf,sizeof(dis));
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
add_edge(u,v,w);
}
spfa();
if(dis[n]==inf)
cout <<"impossible";
else
cout <<dis[n];
return 0;
}