spfa:能解决单源最短路的问题,它是Bellman-ford的队列优化,复杂度最好情况为O(M)所以在某些情况下可以解决优化的dijkstra,并且能够处理负边以及负权环的问题,但在稠密图中SPFA算法时间复杂度在网状情况会退化到(NM)。
Bellman-ford:是每次仅对最短路程发生变化了的点的相邻边进行松弛操作,但是由于不知道哪些点的最短路发生了变化,所以要对每条边都进行一次循环,并且因为在n个点的图中,任意两点的最短路径最多包含n-1条边,还要检查每条边是否对当前顶点能够进行松弛,所以复杂度为O(n*m)。
spfa:只对发生了变化的点进行压缩,对这个点进行入队操作,将这个点的出边进行松弛操作即可。
具体操作步骤:
1,将源点压入队列
2,取出队头元素,对该元素的各邻接边进行松弛操作。如果该点没有在队列中,就压入队列
3,重复步骤 1.
- 这里更新一种最简洁快速的存图方式:用数组来进行模拟
int head[N],e[N],ne[N],idx; //数组存储图四件套。
//head表示每个以每个顶点的头,e存储每个节点的数据,ne存储每个节点下一个数据的下标,idx表示当前是第几个节点
存图
memset(head,-1,sizeof head);//给head赋初值-1,表示头结点为空
void add()
{ int a,b;
cin>>a>>b; //a,b表示一条a->b的边
e[idx]=b; // 将出边顶点存入e[]中第idx个结点
ne[idx]=head[a]; //第ixd个结点的指针域都是指向头的地址
head[a]=idx++; //头以前插法保留当前节点的下标
}
遍历
for(int i=head[x];i!=-1;i=ne[i]) //每次都用i保存head[]的下标,这里就是指向以x为头的下标
{
int y=e[i]; //取出第i个节点的数据放入y中
in[y]--;
if(in[y]==0)
q.push(y);
}
- 链式向前星
- 数据结构
struct node //链式向前星的结构
{
int to,w,next; //终点、权值、以及模拟的next指针
} edge[N];
- 构造:
void add() //链式向前星创建邻接表
{
memset(head,-1,sizeof(head));//将每个头顶点都设置为-1表示空
for(int i=0; i<m; i++)
{
int a,b,c;
cin>>a>>b>>c;
edge[i].to=b;//终点
edge[i].w=c; //权值
edge[i].next=head[a]; //以头插法插入节点
head[a]=i; //头指向第一个节点,保留它为第几个(下标)
}
}
- 遍历:
for(int i=head[t]; i!=-1; i=edge[i].next) { cout<<edge[i].to<<edge[i].w; }
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N=1e5+10;
int head[N],dist[N],book[N];
int n,m;
queue<int >q;
struct node //链式向前星的结构
{
int to,w,next; //终点、权值、以及模拟的next指针
} edge[N];
void add() //链式向前星创建邻接表
{
memset(head,-1,sizeof(head));//将每个头顶点都设置为-1表示空
for(int i=0; i<m; i++)
{
int a,b,c;
cin>>a>>b>>c;
edge[i].to=b;//终点
edge[i].w=c; //权值
edge[i].next=head[a]; //以头插法插入节点
head[a]=i; //头指向第一个节点
}
}
int main()
{
cin>>n>>m;
add();
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
q.push(1);
book[1]=1;
while(q.size())
{
int t=q.front(); //每次弹出一个元素
q.pop();
book[t]=0; //弹出的元素标记没在队列内出现过
for(int i=head[t]; i!=-1; i=edge[i].next) //对以t为头的链表进行遍历
{
int j=edge[i].to;
int w=edge[i].w;
if(dist[j]>dist[t]+w) //如果能够压缩1到j的距离就压缩
{
dist[j]=dist[t]+w;
if(book[j]==0)//并且在压缩完结点j,说明j结点的距离发生了变化,如果它不在队中就将它入队
{
q.push(j);
book[j]=1;
}
}
}
}
if(dist[n]==0x3f3f3f3f)
cout <<"impossible";
else
cout <<dist[n];
}
-
首先将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 <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N=1e5+10;
int n,m,book[N],dist[N],head[N];
int cnt[N]; //每个顶点的边数
struct node
{
int to,w,next;
}edge[N];
void add()
{ memset(head,-1,sizeof(head));
for(int i=0;i<m;i++)
{ int a,b,c;
cin>>a>>b>>c;
edge[i].to=b;
edge[i].w=c;
edge[i].next=head[a];
head[a]=i;
}
}
int main()
{
cin>>n>>m;
int flag=1;
add();
queue<int >q;
for(int i=1;i<=n;i++)
{
q.push(i);
book[i]=1;
}
while(q.size())
{
int u=q.front();
q.pop();
book[u]=0;
for(int i=head[u];i!=-1;i=edge[i].next)
{ int v=edge[i].to,w=edge[i].w;
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;
}
if(book[v]==0)
{
q.push(v);
book[v]=1;
}
}
}
if(flag==0)
break;
}
if(flag==0)
cout <<"Yes";
else cout <<"No";
}