之前由于觉得博客写起来没有笔记方便,所以停了很久。
最近开始使用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]的预处理步骤:
- 新建一个辅助点k, k与所有图中的点相连
- 从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 ~
算法步骤
- 对于图G,新增一个顶点s,以s为弧尾,图中所有点为弧头连边,获得新图G'
- 以s为源点,对G‘利用Bellman-Ford(SPFA)跑一次单源最短路径,获得h[]
如果遇到了负权环,停止。
但不用担心: 负权环不会因为s的创建而出现,因为所有边都是从s来的 - 将图中的所有边reweight: new_weight = w(u,v) + h[u] - h[v]
- 将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); }
来源:https://www.cnblogs.com/GorgeousBankarian/p/12210466.html