Johnson 全源最短路径算法学习笔记

2023-05-01,,

Johnson 全源最短路径算法学习笔记

如果你希望得到带互动的极简文字体验,请点这里

我们来学习johnson

Johnson 算法是一种在边加权有向图中找到所有顶点对之间最短路径的方法。它允许一些边权重为负数,但可能不存在负权重循环。它的工作原理是使用Bellman-Ford 算法来计算输入图的转换,该转换去除了所有负权重,从而允许在转换后的图上使用Dijkstra 算法。Johnson 算法是一种在边加权有向图中找到所有顶点对之间最短路径的方法。它允许一些边权重为负数,但可能不存在负权重循环。它的工作原理是使用Bellman-Ford 算法来计算输入图的转换,该转换去除了所有负权重,从而允许在转换后的图上使用Dijkstra 算法。

--维基百科

可以发现排序算法中最能够全源的只有Johnson 和 Floyd

最优情况:

全源最短路跑\(n\) 次 Bellman-Ford 算法解决,时间复杂度是\(O(n^2m)\),原始是Floyd的\(O(n^3)\)

我们可以更优,使用迪杰斯特拉\(O(nm \space logm)\)

但是我们还关注能否处理负边

所以只有SPFA,Floyd,贝尔曼,Johnson可以解决

原理解释

所以我们解决带负边的全源最短路时同时兼顾时间应该怎么做?

使用Dijkstra

但是Dijkstra不能处理有负边的问题,所以我们可以使边变为正数,我们基本得到3个猜想:

    对每条边进行相同的增量,使所有边非负。
    改变图的结构,不改变图的性质,使Dijkstra可做。
    对每条边单独设计增量,使图的性质保证并且全图非负。

可以看出只有第三种是正确的。

为什么第一种不正确?

如上图(箭头错,1->3应该是3->1)3->2的最短路原本是-2(走上面),但是对于边权+5的图(绿)就成了9(走下面),路径发生了改变,不可取。

Johnson算法核心

建一个超级源,到所有点连0 权边,对超级源跑一遍SPFA,求出每个点的dis 值。重构原图边权,对于边 , , ,将其边权修改为 + dis − dis 。

由三角不等式: + dis ≥ dis ,从而新的边权一定是非负数。

在新图上跑全源 Dijkstra,以 \(u_0\) 为起点,假设经过 \(u_1\), \(u_2\), … , \(u_{t-1}\) 到达 \(u_t\) 。

则经过的边权分别为 $w_{12} $ \(-dis u_2 + w_{2,3}\) $ + dis u_2 − dis u_3 + ⋯ + $ $w_{t−1},t + dis u_{t−1} -dis u_t $

\[= w_{1,2} + w_{2,3} + ⋯ + w _{t−1,t} + (dis u_1 − dis u_t )
\]

只需将跑出的最短路结果做 \(− (dis u_1 − dis u_t )\) 变换就可以得到真实最短路。

正确性

为什么我们使用这个方法是正确的

在重新加权的图中,节点对s和t之间的所有路径都添加了相同的数量\(h ( s ) - h ( t )\)。

正确当且仅当重新加权后的最短路径是原始的最短路径。

\(dis_v \ge dis_u+e_w\)因此,不可能有负边:如果边 \(u \to v\) 在重新加权后具有负权重,那么从 \(q \to u\) 的零长度路径与这条边将形成从 \(q \to v\) 的负长度路径,这与以下事实相矛盾所有顶点到 \(q\) 的距离为零。

负边的不存在确保了 Dijkstra 算法找到的路径的最优性。

原始图中的距离可以通过逆重加权变换从重加权图中的Dijkstra算法计算出的距离计算出来。

Johnson 算法的前三个阶段如下图所示

图中左侧的图形有两个负边,但没有负循环。

中心图显示了新的顶点 \(q\) ,一个最短路径树,由 Bellman-Ford 算法计算,\(q\) 作为起始顶点,每个其他节点计算的值 \(h ( v )\) 作为从 \(q\) 到该节点的最短路径的长度节点。

请注意,这些值都是非正数,因为q到每个顶点都有一条长度为零的边,并且最短路径不能长于该边。

右侧显示了重新加权的图,通过替换每个边的权重形成 \({\displaystyle w(u,v)}{\displaystyle w(u,v)}\) 由 \(w ( u , v ) + h ( u ) − h ( v )\)。

在这个重新加权的图中,所有边的权重都是非负的,但任意两个节点之间的最短路径使用与原始图中相同两个节点之间的最短路径相同的边序列。该算法最后将 Dijkstra 算法应用于重新加权图中的四个起始节点中的每一个。

#include<bits/stdc++.h>
#define ll long long
#define fd(i, a, b) for (ll i = a; i >= b; i--)
#define r(i, a) for (ll i = fir[a]; i; i = e[i].nex)
#define file(a) freopen(#a ".in", "r", stdin);
#define il inline
#define gc getchar()
#define f(i,a,b) for(ll i=a;i<=b;i++)
using namespace std;
const ll maxn=3e5+10,INF=1e16;
il ll read(){
ll x=0,f=1;char ch=gc;
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=gc;}
while(ch>='0'&&ch<='9') x=(x*10)+(ch^48),ch=gc;
return x*f;
}
ll fir[maxn],n,m,cnt;
struct edge{ll to,nex,w;}e[maxn<<1];
il void add(ll a,ll b,ll c){e[++cnt].to=b,e[cnt].w=c,e[cnt].nex=fir[a];fir[a]=cnt;}
bool vis[maxn];
ll far[maxn];
ll num[maxn];
il bool spfa(ll x){
queue<ll> q;
memset(far,0x7f,sizeof(far));
far[x]=0;
q.push(x);
while(!q.empty()){
ll u=q.front();
// cout<<far[u]<<endl;
// cout<<u<<endl;
vis[u]=0;
if(++num[u]>n) return 0;
q.pop();
r(i,u){
ll v=e[i].to;
// cout<<e[i].w<<endl;
if(far[v]>far[u]+e[i].w){
far[v]=far[u]+e[i].w;
// cout<<far[v]<<endl;
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
}
return 1;
}
priority_queue<pair<ll,ll> >q;
ll dis[maxn],cop;
il void dijkstra(ll x){
memset(dis,0x7f,sizeof(dis));
memset(vis,0,sizeof(vis));
cop=dis[0];
dis[x]=0;
q.push(make_pair(0,x));
while(!q.empty()){
ll u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
r(i,u){
ll v=e[i].to;
// cout<<e[i].w<<endl;
if(dis[v]>dis[u]+e[i].w){
dis[v]=dis[u]+e[i].w;
q.push(make_pair(-dis[v],v));
}
}
}
}
ll Add=1e9;
int main()
{
n=read(),m=read();
f(i,1,m){
ll x=read(),y=read(),z=read();
add(x,y,z);
}
f(i,1,n) add(0,i,0);
// cout<<1;
if(spfa(0)){
f(i,1,n) r(j,i){
// cout<<far[i]-far[e[j].to]<<endl;
e[j].w+=far[i]-far[e[j].to];
// cout<<e[j].w<<endl;
}
// f(i,1,n) cout<<far[i]<<" ";
// cout<<endl;
f(i,1,n){
ll ans=0;
dijkstra(i);
f(j,1,n){
if(dis[j]==cop) ans+=j*Add;
else ans+=j*(dis[j]+far[j]-far[i]);
}
printf("%lld\n",ans);
}
}
else printf("-1");
}

参考文献

1.Johnson's algorithm--wikipedia

Johnson 全源最短路径算法学习笔记的相关教程结束。

《Johnson 全源最短路径算法学习笔记.doc》

下载本文的Word格式文档,以方便收藏与打印。