版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011815404/article/details/83904707
【Ford 算法】
1.概述
Bellman-Ford算法适用于计算单源最短路径,即:只能计算起点只有一个的情况。
其最大特点是可以处理存在负边权的情况,但无法处理存在负权回路的情况。
其时间复杂度为:O(N*E),其中,N 是顶点数,E 是边数。
2.算法描述
设起点为 s,dis[v] 表示从 s 到 v 的最短路径,u[i] 和 v[i] 分别表示第 i 条边的起点和终点,w[j] 是连接 u、v 的边 j 的长度。
初始化:
dis[s]=0,dis[v]=0x3f3f3f3f(v≠s),即:初始化为一极大值
算法主体:
void Bellman_Ford()
{
for(int i=0;i<n;i++)
dis[i]=INF;
dis[0]=0;
for(int i=1;i<=n-1;i++)//枚举除终点外的所有点
for(int j=1;j<=m;j++)//枚举所有边
{
int x=u[j];//边j的起点
int y=v[j];//边j的终点
if(dis[x]<INF)//松弛
dis[y]=min(dis[y],dis[x]+w[j]);
}
}
算法结束:dis[v] 即为 s 到 v 最短距离
3.算法分析
一开始认为起点是白点(dis[1]=0),每一次都枚举所有的边,必然会有一些边,连接着白点和蓝点。因此每次都能用所有的白点去修改所有的蓝点,每次循环也必然会有至少一个蓝点变成白点。
以下图为例
令起点为白点,即:dis[1]=0,dis[2、3、4、5]=∞
遍历所有边,将与白点相连的蓝点变为白点,即:dis[1]=0,dis[2]=2,dis[3]=1,dis[4]=2,dis[5] =∞
继续向下遍历,修改蓝点为白点,即:dis[1]=0,dis[2]=2,dis[3]=1,dis[4]=2,dis[5] =4
4.模版
1)简单版
#define INF 0x3f3f3f3f
int n,m;//点数,边数,编号都从0开始
int w[N];//w[i]表示第i条边的权值(距离)
int u[N],v[N];//u[i]和v[i]分别表示第i条边的起点和终点
int dis[N];//单源最短路径
//计算以s为源点的单源最短距离
void Bellman_Ford(int s)
{
for(int i=0;i<n;i++)
dis[i]=INF;
dis[s]=0;
for(int i=1;i<=n-1;i++)//枚举除终点外的所有点
for(int j=1;j<=m;j++)//枚举所有边
{
int x=u[j];//边j的起点
int y=v[j];//边j的终点
if(dis[x]<INF)//松弛
dis[y]=min(dis[y],dis[x]+w[j]);
}
}
2)负环判断版
int n,w,m;
int dis[N];
int k;
struct Node{
int start;
int endd;
int value;
}edge[N];
void add(int u,int v,int w)
{
edge[k].start=u;
edge[k].endd=v;
edge[k].value=w;
k++;
}
bool Ford(int s)
{
memset(dis,INF,sizeof(dis));
dis[s]=0;
for(int i=0;i<n;i++)
{
bool flag=true;
for(int j=0;j<k;j++)
{
int u=edge[j].start;
int v=edge[j].endd;
int w=edge[j].value;
if(dis[u]+w<dis[v])
{
flag=false;
dis[v]=dis[u]+w;
if(i==n-1)
return true;
}
}
if(flag)
return false;
}
return false;
}
3)队列版
#define INF 0x3f3f3f3f
int n,m; //点数,边数,编号都从0开始
int w[N]; //w[i]表示第i条边的权值(距离)
int first[N],next[N];//first[i]和next[i]分别表示第i个点的前驱点与后继点
int u[N],v[N]; //u[i]和v[i]分别表示第i条边的起点和终点
int dis[N]; //单源最短路径
bool vis[N]; //vis[i]标记是否在队列中
//Ford算法队列实现,求从0点到其他点的单源最短路径
void Bellman_Ford()
{
memset(vis,false,sizeof(vis));
memset(dis,INF,sizeof(dis));
dis[0]=0;
queue<int> q;
q.push(0);
while(!q.empty())
{
int x=q.front();
q.pop();
vis[x]=false;//释放对v的标记,可以重新入队
for(int e=first[x];e!=-1;e=next[e])//对于与队首相连的每一条边
if(dis[v[e]]> dis[x]+w[e])//松弛操作
{
dis[v[e]] = dis[x]+w[e];
//不在队列,则加入队列
if(!vis[v[e]])
{
vis[v[e]]=true;
q.push(v[e]);
}
}
}
}
【SPFA】
1.概述
SPFA 是 Ford 算法的队列实现,其与 Ford 算法最大的不同是,其可以判断负环。
其时间复杂度可达:O(k*E),其中,E 是边数,k 是常数,平均值是 2。
2.基本思想
初始时将起点加入队列,每次从队列中取出一个元素,并对所有与它相邻的点进行修改,若相邻的点修改成功,则将其入队,直到队列为空时算法结束。
3.模版
1)可判负环标准版
SPFA 的标准版,可以进行负环判断
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3ff
struct Edge{
int from;
int to;
int dis;
Edge(int f,int t,int d):from(f),to(t),dis(d){}
};
struct BellmanFord
{
int n,m; //点数和边数,编号都从0开始
vector<Edge> edges; //边列表
vector<int> G[N];//每个节点出发的边编号(从0开始编号)
bool inq[N]; //是否在队列中
int d[N]; //s到各个点的距离
int p[N]; //最短路中的上一条弧
int cnt[N]; //进队次数
void init(int n)//初始化
{
this->n=n;
for(int i=0;i<n;i++)
G[i].clear();
edges.clear();
}
void AddEdge(int from,int to,int dis)//添加边
{
edges.push_back(Edge(from,to,dis));
m = edges.size();
G[from].push_back(m-1);
}
//计算以s为源点的最短路径
//如果图中存在s能到达的负圈,那么返回true
bool negativeCycle(int s)
{
memset(inq,0,sizeof(inq));
memset(cnt,0,sizeof(cnt));
memset(d,INF,sizeof(d));
d[s]=0;
queue<int> Q;
Q.push(s);
while(!Q.empty())
{
int u=Q.front(); Q.pop();
inq[u]=false;
for(int i=0;i<G[u].size();i++)
{
Edge &e=edges[G[u][i]];
if(d[e.to] > d[u]+e.dis)
{
d[e.to] = d[u]+e.dis;
p[e.to] = G[u][i];
if(!inq[e.to])
{
Q.push(e.to);
inq[e.to]=true;
if(++cnt[e.to]>n)
return true;
}
}
}
}
return false;
}
}BF;
2)以空间换时间的快速版
使用邻接表实现,避免了 vector 添加边信息的时间消耗
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3ff
struct Edge{
int from;
int to;
int dis;
Edge(int f,int t,int d):from(f),to(t),dis(d){}
};
struct BellmanFord
{
int n,m;
int head[N]; //每个节点邻接表的头
int next[N]; //每个点邻接的边
Edge edges[N]; //所有的边信息
bool inq[N]; //是否在队列中
int d[N]; //s到各个点的距离
int p[N]; //最短路中的上一条弧
int cnt[N]; //进队次数
void init(int n)//初始化
{
this->n=n;
this->m=0;
memset(head,-1,sizeof(head));
}
void AddEdge(int from,int to,int dis)//添边
{
edges[m]=Edge(from,to,dis);
next[m]=head[from];
head[from] = m++;
}
//计算以s为源点的最短路径
//如果图中存在s能到达的负圈,那么返回true
bool negativeCycle(int s)
{
memset(inq,0,sizeof(inq));
memset(cnt,0,sizeof(cnt));
memset(d,INF,sizeof(d));
d[s]=0;
queue<int> Q;
Q.push(s);
while(!Q.empty())
{
int u=Q.front(); Q.pop();
inq[u]=false;
for(int i=head[u];i!=-1;i=next[i])
{
Edge &e=edges[i];
if(d[e.to] > d[u]+e.dist)
{
d[e.to] = d[u]+e.dist;
p[e.to] = i;
if(!inq[e.to])
{
inq[e.to]=true;
Q.push(e.to);
if(++cnt[e.to]>n)
return true;
}
}
}
}
return false;
}
}BF;
【例题】
- Wormholes(POJ-3259)(Ford 判负环):点击这里
- Currency Exchange(POJ-1860)(Ford 求递增环):点击这里
- Skiing(POJ - 3037)(图的构建+SPFA 求最短路):点击这里
- In Action(HDU-3339)(01背包+SPFA 求最短路):点击这里
- Telephone Linse(POJ-3662)(二分+SPFA 求最短路):点击这里
- zz's Mysterious Present(HDU-2145)(SPFA 求最短路):点击这里
- 热浪(信息学奥赛一本通-T1379)(SPFA 求最短路):点击这里
- 分糖果(信息学奥赛一本通-T1380)(SPFA 求最短路):点击这里
- 城市路(信息学奥赛一本通-T1381)(SPFA 求最短路):点击这里
- 最短路(信息学奥赛一本通-T1382)(SPFA 求最短路):点击这里
- 香甜的黄油(信息学奥赛一本通-T1345)(SPFA 求最短路):点击这里