Bellman-ford
特点:单源最短路可判环
思路:
如果不存在负环,那么经过n-1轮松弛可以找到所有的最短路。如果第n轮还可以松弛,那么说明存在环。
算法的过程全部演示了一遍,可以加深影响https://www.bilibili.com/video/av43217121
证明:
参考:https://www.cnblogs.com/willaty/p/8243881.html
一。假设某点与源点不连通。
由于初始化时,除了源点距离初始为0之外,其他点都初始化为无穷大,如果不连通,则某点所在的连通图的任一条边都不会导致更新。
二。假设x点与源点连通。
每个点都存在自己的最短路,为(e0, e1, e2, ..., ek)。
显然,源点只要经过n - 1条边就可到达任一点; (一)
现只需证明,对x点,每次迭代(松弛),至少有一条最短边ei的距离被找到,除非已经到达x点。 (二)
对于第一次迭代,必定更新和源点相连的所有边(如果是有向图,则是指出去的),由于源点距离是0,和其相连的都是无穷大。
而这些往外连的边中,必有一条是x点的最短路上起始的一条边。
则设有点k,这个点是x最短路上的一个点,下一次松弛,必能找到下一个点,且也是x的最短路上的一个点:
由于下一次松弛将更新与k相连的所有点。
有(一)(二)可得,上面两次迭代可以找出最短路。
代码:
int const inf = 0x7f7f7f7f;
struct Edge //边集合
{
int from,to,dist;
Edge(){}
Edge(int f,int t,int d):from(f),to(t),dist(d){}
};
vector<Edge>v[N];
v[from].push_back(Edge(from,to,dist));
v[to].push_back(Edge(to,from,dist));
bool bellman_ford(){ //判断是否存在负环
for(int i=1;i<=n;i++) d[i] = inf;
d[1] = 0;
for(int i=1;i<=n;i++){
for(int j=0;j<edge.size();j++){
Edge e = edge[j];
if(d[e.v] > d[e.u] + e.d){
d[e.v] = d[e.u] + e.d;
if(i == n) return true; //如果第n轮还可以松弛,则直接判定存在负环
}
}
}
return false;
}
SPFA
特点:单源最短路可判环
思路:
SPFA是Bellman的扩展。其实Bellman有很多步骤都是做无用功,因为遍历到某一条边可能不会进行更新,可能不需要n-1轮就已经找到所有最短路。SPFA把更新的点加入队列,下一次找这些点更新。如果队列为空,即没有可以更新的,那么就找到了所有的最短路。如果某个点经过n-1此更新后还可以更新,那说明存在环,这和Bellman算法是一样的。因为如果不存在环,经过n-1次是一定能找到最短路的。
证明:
参考:https://www.cnblogs.com/scau20110726/archive/2012/11/18/2776124.html
每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。(证毕)
代码:
bool spfa(int s){
memset(cnt,0,sizeof(cnt));
memset(done,false,sizeof(done));
fill(d,d+N,inf);
queue<int>q;
q.push(s);
done[s] = true;
d[s] = 0;
while(!q.empty()){
int p = q.front(); q.pop();
done[p] = false;
for(int i=0;i<v[p].size();i++){
Edge e = v[p][i];
if(d[e.from] < inf && d[e.to] > d[e.from] + e.dist){
d[e.to] = d[e.from] + e.dist;
if(!done[e.to]){
done[e.to] = true;
q.push(e.to);
if(++cnt[e.to] >= n) return true; //判断负环
}
}
}
}
return false;
}
Dijkstra
特点:单源最短路(边权都为正)
思路:
参考:https://www.cnblogs.com/jason2003/p/7222182.html
Dijkstra 算法是一种类似于贪心的算法,步骤如下:
1、当到一个时间点时,图上部分的点的最短距离已确定,部分点的最短距离未确定。
2、选一个所有未确定点中离源点最近的点,把他认为成最短距离。
3、再把这个点所有出边遍历一边,更新所有的点。
证明:
参考:https://blog.csdn.net/a19990412/article/details/80232810
代码:
朴素版本:
void dijkstra(int s)
{
bool vis[N];
memset(v,false,sizeof(v));
for(int i=1;i<=n;i++)
d[i] = e[s][i];
v[s] = true;
for(int k=1;k<=n-1;k++)
{
int Min = inf,u;
for(int i=1;i<=n;i++) //找离s源点最近的点
{
if(!v[i] && d[i]<Min)
{
Min = d[i];
u = i;
}
}
v[u] = true;
for(int i=1;i<=n;i++)
{
if(e[u][i]<inf)
d[i] = min(d[i],d[u] + e[u][i]);
}
}
}
FIFO优化:
void dijkstra(int s)
{
for(int i=0;i<n;i++) d[i] = (i == s ? 0 : inf);
memset(done,0,sizeof(done));
priority_queue<pii,vector<pii>,greater<pii> >q;
q.push(make_pair(0,s));
while(!q.empty())
{
pii u = q.top(); q.pop();
int x = u.second;
if(done[x] == true) continue;
done[x] = true;
for(int i=0;i<G[x].size();i++)
{
Edge &e = edge[G[x][i]];
if(d[e.to]>d[x] + e.dist)
{
d[e.to] = d[x] + e.dist;
q.push(make_pair(d[e.to],e.to));
}
}
}
}
Floyd算法
特点:任意两点之间的最短路(可以存在负权边但不能负权环),可求最小环。
思路:动态规划:
证明:
floyd算法代码虽然是最简单的,但是我认为证明是最难的。
参考:https://blog.csdn.net/xianpingping/article/details/79947091
代码:
for(int k=0;k<n;k++) //中转站
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
if(e[i][k]<inf && e[k][j]<inf) //注意这里不要忘记了!
e[i][j] = min(e[i][j],e[i][k]+e[k][j]);
}