注:阅读此篇默认你已经知晓了松弛等相关概念,博主建议你最好可以先学会最简单的Bellman-ford算法再来学这篇,这样效果会好很多。Bellman-ford博文指路:https://blog.csdn.net/hzf0701/article/details/107686720
SPFA算法是Bellman-ford算法队列优化的别称,通常用于求含负权边的单源最短路径,以及判负权环。SPFA 最坏情况下复杂度和朴素 Bellman-Ford 相同,为 O(VE)。思想和Bellman算法一样,相当于Bellman-ford算法的进阶版。
适用条件:
SPFA则为针对Bellman-ford的缺点做的使每次做的松弛操作更有效的优化。发现在Bellman-ford第一次大松弛中只有松弛从源点出来的边才是有效的,松弛时当边的起点有效(被松弛过)时松弛操作才有效。那我们就不妨每次都只做有效的松弛操作,建一个队列,开始时将源点入队,每次从队头弹出head,尝试松弛所有head的邻接点,若松弛成功且该邻接点不在队中,就把它入队;否则什么也不干。时间复杂度为O(vm),v是一个平均值为2的常数。(因为当我们每次都做有效的松弛操作时,就会发现跑最短路快了很多,在大多数情况下可以吊打各个最短路算法。当然也特别容易被特殊数据卡,此时时间复杂度容易退化为O(nm))。类似的,spfa每做“一层”(层的概念类似于BFS的层)松弛操作就会确定所有最短路的一条边,故最极限的情况spfa做n层松弛操作就可以求出一个单源最短路了。
举个例子:
注:SPFA算法保证每个顶点至多入队列n次(n为顶点总数)更新出到所有点的最短距离,如果某个点入队列次数大于n次表示图中有负环,所以我们这里开了一个 数组cnt[maxn]来记录每个顶点的入队次数。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=105;
const int inf=0x3f3f3f3f;
int n,m;//顶点数与边数。
int graph[maxn][maxn];//针对稠密图。
int dis[maxn];//存储所有顶点到源点的距离。
int cnt[maxn];//cnt[i]记录的是i顶点的入队次数。若大于n,则必然存在负环。
bool visited[maxn];//判断是否在队列中。
void init(){
memset(graph,inf,sizeof(graph));
memset(dis,inf,sizeof(dis));
memset(visited,false,sizeof(visited));
memset(cnt,0,sizeof(cnt));
}
int spfa(int S){
queue<int> q;//顶点队列
dis[S]=0;
q.push(S);//源点入队。
cnt[S]++;
int temp;
visited[S]=true;
int flag=0;//判断是否存在负环
while(!q.empty()){
temp=q.front();
q.pop();
visited[temp]=false;//已经出队,改标志位。
//松弛操作
for(int i=1;i<=n;i++){
if(dis[i]>dis[temp]+graph[temp][i]){
dis[i]=dis[temp]+graph[temp][i];//更新最短路径
if(!visited[i]){
q.push(i);
cnt[i]++;
if(cnt[i]>n){flag=1;return flag;}
visited[i]=true;
}
}
}
}
return flag;
}
int main(){
while(cin>>n>>m){
int u,v,w;
init();
for(int i=0;i<m;i++){
cin>>u>>v>>w;
graph[u][v]=w;
graph[v][u]=w;
}
int S,E;//起点与终点。
cin>>S>>E;
int result=spfa(S);
if(result)cout<<"存在负环"<<endl;
else cout<<dis[E]<<endl;
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=105;//顶点最大数
const int M=1e4;//边最大数
const int inf=0x3f3f3f3f;
int n,m;//顶点数与边数。
int tot;//递增序号,用来存储。
int cnt[maxn];//cnt[i]记录的是i顶点的入队次数。若大于n,则必然存在负环。
typedef struct Edge{
int next;//当前边的下一条边。
int to;//边的终端节点
int w;//边权值。
}Edge;
Edge edge[M];//用边来表示图。
int head[maxn];//head[i]表示以i为起点的第一条边的编号,若为-1则表示以该起点没有边。
int dis[maxn];//存储所有顶点到源点的距离。
bool visited[maxn];//判断是否在队列中。
void init(){
memset(dis,inf,sizeof(dis));
memset(visited,false,sizeof(visited));
memset(head,-1,sizeof(head));
memset(cnt,0,sizeof(cnt));
tot=0;
}
void add(int u,int v,int w){
edge[tot].to=v;
edge[tot].w=w;
edge[tot].next=head[u];//插在第一条边前面。
head[u]=tot++;//改变第一条边的编号。
}
int spfa(int S){
queue<int> q;
dis[S]=0;
int temp;
q.push(S);
cnt[S]++;
visited[S]=true;
int flag=0;//标志位,判断是否存在负环。
while(!q.empty()){
temp=q.front();
q.pop();
visited[temp]=false;
int v,w;
for(int i=head[temp];i!=-1;i=edge[i].next){
v=edge[i].to;//保存边的终端节点。
w=edge[i].w;//保存边权值。
if(dis[v]>dis[temp]+w){
//松弛操作。
dis[v]=dis[temp]+w;
if(!visited[v]){
q.push(v);
cnt[v]++;
if(cnt[v]>n){flag=1;return flag;}
visited[v]=true;
}
}
}
}
return flag;
}
int main(){
while(cin>>n>>m){
int u,v,w;
init();
for(int i=0;i<m;i++){
cin>>u>>v>>w;
add(u,v,w);//这里针对的是无向图,有向图加边只加一次。
add(v,u,w);
}
int S,E;//起点与终点。
cin>>S>>E;
int result=spfa(S);
if(result)cout<<"存在负环"<<endl;
else cout<<dis[E]<<endl;
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=105;//顶点最大数
const int M=1e4;//边最大数
const int inf=0x3f3f3f3f;
int n,m;//顶点数与边数。
int cnt[maxn];//cnt[i]记录的是i顶点的入队次数。若大于n,则必然存在负环。
typedef struct Edge{
int to;//边的终端节点
int w;//边权值。
Edge(int to,int w):to(to),w(w){}//构造函数
}Edge;
vector<Edge> graph[maxn];//用vector模拟邻接表
int dis[maxn];//存储所有顶点到源点的距离。
bool visited[maxn];//判断是否在队列中。
void init(){
memset(cnt,0,sizeof(cnt));
memset(dis,inf,sizeof(dis));
memset(visited,false,sizeof(visited));
}
int spfa(int S){
queue<int> q;
dis[S]=0;
int temp;
q.push(S);
cnt[S]++;
visited[S]=true; //入队即true。
int flag=0; //标志,若为真,则表示存在负环。
while(!q.empty()){
temp=q.front();
q.pop();
visited[temp]=false; //出队则false。
int v,w;
int t=graph[temp].size();//避免多次调用此函数。
//松弛操作
for(int i=0;i<t;i++){
v=graph[temp][i].to;
w=graph[temp][i].w;
if(dis[v]>dis[temp]+w){
dis[v]=dis[temp]+w;//更新最短路径
if(!visited[v]){
//判断是否在队列中
q.push(v);
cnt[v]++;
if(cnt[v]>n){flag=1;return flag;}
visited[v]=true;
}
}
}
}
return flag;
}
int main(){
while(cin>>n>>m){
int u,v,w;
init();
for(int i=0;i<m;i++){
cin>>u>>v>>w;
graph[u].push_back(Edge(v,w));//这里针对的是无向图,有向图加边只加一次。
graph[v].push_back(Edge(u,w));
}
int S,E;//起点与终点。
cin>>S>>E;
int result=spfa(S);
if(result)cout<<"存在负环"<<endl;
else cout<<dis[E]<<endl;
}
return 0;
}