之前由于觉得博客写起来没有笔记方便,所以停了很久。
最近开始使用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);
}