Bellman–Ford:对途中的边进行V-1轮操作,每一轮操作都遍历所有的边,对每条边u->v,如果以u为中介点可以使d[v]更小就优化。此时,如果没有从源点可达的负环,那么数组d中的所有值应该已经达到最优。因此,再对所有边进行一轮操作,判断是否u->v仍然可以进行松弛操作,如果可以,说明有从源可达的负环,返回false,否则,说明数组d中的所有值已经达到最优,返回true;
只要进行n - 1 轮就可以了,因为在一个含有n个顶点的图中,任意两点之间的最短路径最多包含n-1条边。
我们这个算法真的就只能包含n-1条边吗?最短路径中不能包含回路吗?
答案是:不可能!最短路径肯定是一个不包含回路的简单路径。回路分为正权回路(即回路权值之和为正)和负权回路(即回路权值之和为负)。我们分别来讨论一下为什么这两种回路都不可能有。如果最短路径中包含正权回路,那么去掉这个回路,一定可以得到更短的路径。如果最短路径中包含负权回路,那么肯定没有最短路径,因为每多走一次 负权回路就可以得到更短的路径。 因此,最短路径肯定是-一个不包含回路的简单路径,即最多包含n-1条边,所以进行n-1轮松弛就可以了。
Bellman:返回一个bool值,如果存在从源点可达的负环,返回false,否则返回true
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000;
const int INF = 100000;
struct node
{
int v, dis;
};
vector<node> Adj[maxn];
int n;
int d[maxn];
//返回一个bool值,如果存在从源点可达的负环,返回false,否则返回true
bool Bellman(int s)
{
fill(d, d + maxn, INF);
d[s] = 0;
//求解数组d的部分
for(int i = 0; i < n - 1; i++) //执行n-1轮操作,n为顶点数
{
for(int u = 0; u < n; u++) //每轮遍历所有边
{
for(int j = 0; j < Adj[u].size(); j++)
{
int v = Adj[u][j].v; //邻接边的顶点
int dis = Adj[u][j].dis; //邻接边的边权
if(d[u] + dis < d[v]) //以u为终结点可以使d[v]更小
{
d[v] = d[u] + dis; //松弛操作
}
}
}
}
for(int u = 0; u < n; u++) //对每条边进行判断
{
for(int j = 0; j < Adj[u].size(); j++)
{
int v = Adj[u][j].v; //邻接边的顶点
int dis = Adj[u][j].dis; //邻接边的边权
if(d[u] + dis < d[v]) //如果仍可以被松弛
{
return false; //说明图中又从源点可达的负环
}
}
}
return true; //数组d的所有值都已经达到最优
}
SPFA
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000;
const int INF = 100000;
struct node
{
int v, dis;
};
vector<node> Adj[maxn];
int n, d[maxn], num[maxn]; //num记录顶点的入队次数
bool inq[maxn] = {false}; //顶点是否在队列中
bool SPFA(int s)
{
//初始化
memset(num, 0, sizeof(num));
fill(d, d + maxn, INF);
//源点入队
queue<int> q;
q.push(s);
inq[s] = true; //标记已经入队
num[s]++; //源点入队次数+1
d[s] = 0;
//主体部分
while(!q.empty())
{
int u = q.front();
q.pop();
inq[u] = false; //设置u不在队列中
for(int j = 0; j < Adj[u].size(); j++)
{
int v = Adj[u][j].v;
int dis = Adj[u][j].dis;
//松弛操作
if(d[u] + dis < d[v])
{
d[v] = d[u] + dis;
if(!inq[v]) //如果不在队列中
{
q.push(v); //v入队
inq[v] = true; //设置v在队列中
num[v]++; //v的入队次数+1
//某个顶点的入队次数超过V-1(V是顶点个数),说明存在从源点可达的负环
if(num[v] >= n) return false; //有可达负环
}
}
}
}
return true; //无可达负环
}
int main()
{
return 0;
}
SPFA:**只有某个顶点d[u]的值变化时,从它出发的边的邻接点v的d[v]值才有可能改变。**由此可以进行一个优化:用一个队列,每次将队首顶点u取出,然后对从u出发的所有边u->v进行松弛操作,如果可松弛,优化d[v],此时如果v不在队列中,就把v加入队列。这样操作知道队列为空(说明没有负环),或者某个顶点的入队次数超过V-1(有负环)。
const int maxn = 1000;
const int INF = 100000;
struct node
{
int v, dis;
};
vector<node> Adj[maxn];
int n, d[maxn], num[maxn]; //num记录顶点的入队次数
bool inq[maxn] = {false}; //顶点是否在队列中
bool SPFA(int s)
{
//初始化
memset(num, 0, sizeof(num));
fill(d, d + maxn, INF);
//源点入队
queue<int> q;
q.push(s);
inq[s] = true; //标记已经入队
num[s]++; //源点入队次数+1
d[s] = 0;
//主题部分
while(!q.empty())
{
int u = q.front();
q.pop();
inq[u] = false; //设置u不在队列中(易漏点)
for(int j = 0; j < Adj[u].size(); j++)
{
int v = Adj[u][j].v;
int dis = Adj[u][j].dis;
//松弛操作
if(d[u] + dis < d[v])
{
d[v] = d[u] + dis;
if(!inq[v]) //如果不在队列中
{
q.push(v); //v入队
inq[v] = true; //设置v在队列中
num[v]++; //v的入队次数+1
//某个顶点的入队次数超过V-1(V是顶点个数),说明存在从源点可达的负环
if(num[v] >= n) return false; //有可达负环
}
}
}
}
return true; //无可达负环
}