Johnson's Algorithm for ASPS(All Pairs Shortest Path)

之前由于觉得博客写起来没有笔记方便,所以停了很久。
最近开始使用markdown来写博客,感觉挺清爽,又要开始增产啦~

Johnson's Algorithm = Bellman-Ford + Dijkstra

若是仅仅使用|V|次Dijkstra算法跑完全图可以求得最短路径,时间复杂度(V2log|V| + VE)
虽然效率比Floyd高,但是只能处理正权图

Johnson算法就是将所有边预处理重新赋值,使全部边权为正,再利用Dijkstra处理

注意:不能单纯地每条边加上某个权值w,这样会改变最短路径

    Example: s --> t
            1        1       1
        s ------ a ----- b ----- t
          \____________________/
                    4
    初始最短路,s-->a-->b-->t
    若全部边同时+1,最短路变成s-->t
    所以不能以边为单位来进行权值相加,应以**路径**为单位进行边的相加

Johnson算法Bellman-Ford预处理:

  • 赋予每个顶点权值h[v]
  • 对于边e(u,v),reweight之后的边权 = w(u,v) + h[u] - h[v]
  • 那么对于u,v之间的任意路径增加的总权值是一样的(h[t]-h[s]),且每条边的边权>=0

获得h[i]的预处理步骤:

  1. 新建一个辅助点k, k与所有图中的点相连
  2. 从k到原图中各点i的最短距离就是h[i]

为何这么处理就能够使得边权都 >= 0 ?

由于利用Bellman-Ford预处理获得以k为源点的全图单源最短路径
所以对于边e(u,v): h[v] <= h[u] + w(u,v) ==> w(u,v) + h[u] - h[v] >= 0
wow that's amazing ~

算法步骤

  1. 对于图G,新增一个顶点s,以s为弧尾,图中所有点为弧头连边,获得新图G'
  2. 以s为源点,对G‘利用Bellman-Ford(SPFA)跑一次单源最短路径,获得h[]
    如果遇到了负权环,停止。
    但不用担心: 负权环不会因为s的创建而出现,因为所有边都是从s来的
  3. 将图中的所有边reweight: new_weight = w(u,v) + h[u] - h[v]
  4. 将s点删除,对图Dijkstra求解|V|次单源最短路

时间复杂度

一般情况下dijkstra O(|V|log|V|),Bellman-Ford O(|V||E|),一次Bellman预处理然后|V|次单源最短路径==>O(|V|2log|V| + |V||E|),
当图为完全图时 E = O(v2), Johnson算法的时间复杂度和Floyd相同为O(V3)

最终得到 minDis(u, v) = dis[u][v] - (h[u]-h[v])

Pseudo Code

const int V = 1e3;
int h[V], dis[V][V];

void new_graph(Graph* g);
bool Bellman_Ford(Graph* g, int s, int h[]);
void Dijkstra(Graph* g, int s, int dis[], int tool);

void Johnson(Graph* g) {
    new_graph(g); // bellman 预处理
    for(int i=1; i<=V; ++i) { // dijkstra 处理每一个顶点
        Dijkstra(g, i, dis[i], 0);
    }
}

/*
 * @para g 预处理的图G'
 * @para s 源点
 * @para h[] 辅助将边全部重新赋值的数组
 * @para return true没有负权环; 否则有负权环
*/
bool Bellman_Ford(Graph* g, int s, int h[]) { // 队列优化版也称SPFA
    memset(h, Inf, sizeof(h));
    memset(inq, false, sizeof(inq));
    queue<int> q;   q.push(s);
    inq[s] = true, cnt[s] = 1;
    while(q.size()) {
        int u = q.front(); q.pop();
        inq[u] = false;
        for(int i=g->hd[u]; i; i=e[i].nxt) {
            int v = e[i].v;
            if(h[v]!=Inf && h[v] > h[u] + e[i].w) {
                h[v] = h[u] + e[i].w;
                if(!inq[v]) q.push(v);
                if(++cnt[v] > n-1) return false; // 存在负环
            }
        }
    }
}

struct Node {
    int u, dis;
    Node(int a=0, int b=0): u(a), dis(b);
    bool operator < (const Node& b) const {
        return this->dis > b.dis;
    }
};
/*
 * @para tool 预处理时添加的全局源点
 * @para g 经过处理的G', 所以在进行dijkstra的过程中忽略全局源点 tool
*/
void Dijkstra(Graph* g, int s, int dis[], int tool) {
    memset(vis, false, sizeof(vis));
    memset(dis, Inf, sizeof(dis));
    dis[s][s] = true;
    priority_queue<Node> pq;    pq.push(Node(s, 0));
    while(pq.size()) {
        Node now = pq.top();    pq.pop();
        int u = now.u;
        if(vis[u]) continue;
        vis[u] = true;
        for(int i=g->hd[u]; i; i=e[i].nxt) {
            int v = e[i].v;
            if(v!=tool && // 忽略全局源点tool
                    !vis[v] && dis[v] > dis[u] + e[i].w) {
                dis[v] = dis[u] + e[i].w;
                pq.push(Node(v, dis[v]));
            }
        }
    }
}
void new_graph(Graph* g) {
    int s = 0; // 新增全局源点s
    for(int i=1; i<=V; ++i) {
        g->add_edge(s, i, 0);
    }
    Bellman_Ford(g, s, h);
}

猜你喜欢

转载自www.cnblogs.com/GorgeousBankarian/p/12210466.html