全源最短路 Johnson算法

博客介绍了Johnson算法在处理含有负权重边的有向图中寻找全源最短路的应用。通过重新定义权重函数,确保新权重非负,并利用Bellman-Ford和Dijkstra算法作为子程序。文章详细阐述了算法原理,包括如何通过新增虚拟节点和调整权重消除负环,并提供了模板代码实现。
摘要由CSDN通过智能技术生成

Johnson算法

模板题P5905 【模板】Johnson 全源最短路

题意简述:给定一个包含 n n n 个结点和 m m m 条带权边的有向图,求全源最短路,可能有负权重的边重边自环,部分数据卡 SPFA 算法(还特地针对了SLF优化)

题意很明确,就是卡SPFA、Dijkstra、Floyd的

这题用SPFA可以被卡成 O ( n 2 m ) O(n^2m) O(n2m),Dijkstra不能处理负权重的边,Floyd O ( n 3 ) O(n^3) O(n3)肯定超时

说了这么多,就是为了引出我们要讲的 Johnson 算法

Johnson算法可以判断给定的图中有无负环,在无负环时能给出全源最短路时间复杂度 O ( n m log ⁡ n ) O(nm\log n) O(nmlogn)

Johnson算法的核心为重新赋予权值,使得边权变成非负的新边权,而在运行过程中以Bellman-Ford和Dijkstra(以下提到的Dijkstra都用优先队列优化)作为自己的子程序

一、算法原理

以下推导过程部分参照《算法导论》

对于给定有向图 G = ( V , E ) G=(V,E) G=(V,E) ,权重函数为 w : E → R w:E \rightarrow R w:ER ,如果所有的边权重都为非负值,则只需在每一个结点上跑Dijkstra即可,时间复杂度 O ( V E log ⁡ V ) O(VE\log V) O(VElogV);如果存在边权重为负值的边,但没有负环,则我们只需要预处理出一种新的权重函数 w ′ : E → R w':E \rightarrow R w:ER 使得所有的边权重为非负值再跑Dijkstra即可

则新的权重函数 w ′ w' w 一定满足以下两个条件:

  1. 对于结点 u , v ∈ V u,v \in V u,vV ,路径 p p p 为使用权重函数 w ′ w' w u u u v v v 的一条最短路径,当且仅当 p p p 为使用权重函数 w w w u u u v v v 的一条最短路径
  2. 对于任何边 ( u , v ) ∈ E (u,v) \in E (u,v)E w ′ ( u , v ) w'(u,v) w(u,v) 为非负值 (Dijkstra不可以处理负权重的边)

设函数 h : V → R h:V\rightarrow R h:VR 将结点映射到实数上

对于任何边 ( u , v ) ∈ E (u,v) \in E (u,v)E,定义 w ′ ( u , v ) = w ( u , v ) + h ( u ) − h ( v ) w'(u,v) = w(u,v) + h(u) - h(v) w(u,v)=w(u,v)+h(u)h(v)

我们先假设图 G G G 没有负环,设路径 p p p 为使用权重函数 w ′ w' w v 0 v_0 v0 v k v_k vk的一条最短路径 ( v 0 , v k ∈ V ) (v_0,v_k \in V) (v0,vkV) ,当且仅当 p p p 为使用权重函数 w w w v 0 v_0 v0 v k v_k vk 的一条最短路径

则可以证明 w ′ ( p ) = w ( p ) + h ( v 0 ) − h ( v k ) w'(p) = w(p) + h(v_0)-h(v_k) w(p)=w(p)+h(v0)h(vk)

w ′ ( p ) = ∑ i = 1 k w ′ ( v i − 1 , v i ) w'(p)=\sum\limits_{i=1}^{k}{w'(v_{i-1},v_i)} w(p)=i=1kw(vi1,vi)

   = ∑ i = 1 k ( w ( v i − 1 , v i ) + h ( v i − 1 ) − h ( v i ) ) \qquad \ \ = \sum\limits_{i=1}^{k}{(w(v_{i-1},v_i)+h(v_{i-1})-h(v_i))}   =i=1k(w(vi1,vi)+h(vi1)h(vi))

   = ∑ i = 1 k w ( v i − 1 , v i ) + h ( v 0 ) − h ( v k ) \qquad \ \ =\sum\limits_{i=1}^{k}{w(v_{i-1},v_i)}+h(v_0)-h(v_k)   =i=1kw(vi1,vi)+h(v0)h(vk)

   = w ( p ) + h ( v 0 ) − h ( v k ) \qquad \ \ = w(p)+h(v_0)-h(v_k)   =w(p)+h(v0)h(vk)

因为 h h h 为将结点映射到实数上的函数,函数的值不受边权重的影响,所以对于一条为使用权重函数 w w w v 0 v_0 v0 v k v_k vk p p p 要长的路径 q q q ,在使用权重函数 w ′ w' w q q q 一定比 p p p

我们再来看看负环的问题,对于环 c = ⟨ v 0 , v 1 , . . . v k ⟩ c = \langle v_0, v_1, ... v_k\rangle c=v0,v1,...vk ,其中 v 0 = v k v_0 = v_k v0=vk ,则有 w ′ ( c ) = w ( c ) + h ( v 0 ) − h ( v k ) = w ( c ) w'(c) = w(c) + h(v_0) -h(v_k) = w(c) w(c)=w(c)+h(v0)h(vk)=w(c)

则对于 c c c 使用权重函数 w w w 时为负环,当且仅当使用权重函数 w w w 时为负环

那么 h h h 函数该如何处理呢?

我们可以新建一个虚拟结点 s s s ,并将 s s s 和其他所有结点建边权重为 0 0 0 的边,在 s s s 结点跑一次Bellman-Ford(SPFA当然可以),求出 s s s 到其他结点的最短路,则对于任何 v ∈ V v \in V vV h ( v ) h(v) h(v) 的值即为 s s s v v v 的最短路径长,在跑的过程中如果一条路径中某个结点被松弛超过 n n n 次,则一定存在负环,并返回存在负环的信息(SPFA判负环的方法就不延伸了)

因为 s s s 结点入度为 0 0 0 ,所以其他的路径中不会包含 s s s 结点

那么为什么无负环时使用权重函数 w ′ w' w ,边权重一定为非负呢?

设现在的图为 G ′ = ( V ′ , E ′ ) G' = (V',E') G=(V,E) V ′ = V ∪ { s } V' = V \cup \{s\} V=V{s} E ′ = E ∪ { ( s , v ) , v ∈ E } E' = E \cup \{(s,v),v\in E\} E=E{(s,v),vE}

根据三角形不等式,可知对于任意结点 u , v ∈ V ′ u,v \in V' u,vV ,若 ∃ ( u , v ) ∈ E ′ \exists (u,v) \in E' (u,v)E,则有

h ( u ) + w ( u , v ) ≥ h ( v ) ⇒ w ( u , v ) + h ( u ) − h ( v ) ≥ 0 ⇒ w ′ ( u , v ) ≥ 0 h(u) + w(u,v) \ge h(v) \Rightarrow w(u,v) + h(u) - h(v) \ge 0 \Rightarrow w'(u,v) \ge 0 h(u)+w(u,v)h(v)w(u,v)+h(u)h(v)0w(u,v)0

现在我们只需要在每个结点上跑一次Dijkstra即可

时间复杂度 O ( V E log ⁡ V ) O(VE \log V) O(VElogV) (SPFA时间复杂度最坏 O ( V E ) O(VE) O(VE) ,预处理时间复杂度 O ( V E ) O(VE) O(VE)

二、参考代码

模板题代码如下 (注:输出按该题要求写的)

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define gc() getchar()
#define pc(a) putchar(a)
#define INF 1000000000
#define MAXN (int)(3e3+15)
template<typename T>void read(T &k)
{
    char ch=gc();T x=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
    while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gc();}
    k=x*f;
}
template<typename T>void write(T k)
{
    if(k<0){pc('-');k=-k;}
    if(k>9)write(k/10);
    pc(k%10+'0');
}
struct Edge
{
    int u,v,w,next;
}e[MAXN<<2];
struct node
{
    int u,dis;
    bool operator<(const node &o)const
        {return dis>o.dis;}
};
int n,m,pos=1,head[MAXN];
int h[MAXN],d[MAXN],vis[MAXN],cnt[MAXN],ans;
void addEdge(int u,int v,int w)
{
    e[pos]={u,v,w,head[u]};
    head[u]=pos++;
}
bool spfa()
{
    queue<int> q;
    fill(h+1,h+1+n,INF);
    q.push(0);
    vis[0]=1;h[0]=0;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        vis[u]=0;
        for(int i=head[u]; i; i=e[i].next)
        {
            int v=e[i].v;
            if(h[v]>h[u]+e[i].w)
            {
                h[v]=h[u]+e[i].w;
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=1;
                    if(++cnt[v]>n) // n+1结点
                        return 0;
                }
            }
        }
    }
    return 1;
}
void dijkstra(int st)
{
    priority_queue<node> q;
    fill(d+1,d+1+n,INF);
    memset(vis,0,sizeof(vis));
    q.push({st,0});
    d[st]=0;
    while(!q.empty())
    {
        int u=q.top().u;q.pop();
        if(vis[u])
            continue;
        vis[u]=1;
        for(int i=head[u]; i; i=e[i].next)
        {
            int v=e[i].v;
            if(d[v]>d[u]+e[i].w)
            {
                d[v]=d[u]+e[i].w;
                if(!vis[v])
                    q.push({v,d[v]});
            }
        }
    }
}
signed main()
{
    read(n);read(m);
    for(int i=1,u,v,w; i<=m; i++)
    {
        read(u);read(v);read(w);
        addEdge(u,v,w);
    }
    for(int i=1; i<=n; i++)
        addEdge(0,i,0);
    if(!spfa())return puts("-1"),0;
    for(int i=1; i<pos; i++)
        e[i].w+=h[e[i].u]-h[e[i].v];
    for(int i=1; i<=n; i++)
    {
        dijkstra(i);
        int ans=0;
        for(int j=1; j<=n; j++)
        {
            if(d[j]==INF)
                ans+=j*INF;
            else
                ans+=j*(d[j]-(h[i]-h[j]));
        }
        write(ans);pc('\n');
    }
    return 0;
}

转载请说明出处

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值