最短路—Johnson算法(解决负权边,判断负权环)

最短路—Johnson算法(解决负权边,判断负权环)

此算法可以在O(V2lgV+VE)的时间内找到所有结点对之间的最短路径。对于稀疏图而言,Johnson算法表现要由于Floyd算法。且Johnson算法要么返回一个包含所有结点对的最短路径 权重的矩阵,要么报告图中含有负权环

该算法的核心是利用Dijkstra算法和Bellman-ford算法作为自己的子程序来实现的。

在 1977 年,Donald B. Johnson提出了对所有边的权值进行 “re-weight” 的算法,使得边的权值非负进而可以使用Dijkstra算法进行最短路径的计算
那么,Johnson算法是如何对边的权值进行 “re-weight” 的呢?以下面的图 G 为例,有 4 个顶点和 5 条边。

img
首先,新增一个源顶点 4,并使其与所有顶点连通,新边赋权值为 0,如下图所示。
在这里插入图片描述
使用 Bellman-Ford算法 计算新的顶点到所有其它顶点的最短路径,则从 4 至 {0, 1, 2, 3} 的最短路径分别是 {0, -5, -1, 0}。即有 h[] = {0, -5, -1, 0}。当得到这个 h[] 信息后,将新增的顶点 4 移除,然后使用如下公式对所有边的权值进行 “re-weight”:
w(u, v) = w(u, v) + (h[u] - h[v]).
则可得到下图中的结果:
在这里插入图片描述
此时,所有边的权值已经被 “re-weight” 为非负。此时,就可以利用Dijkstra 算法对每个顶点分别进行最短路径的计算了。


Johnson 算法描述如下:

  1. 给定图 G = (V, E),增加一个新的顶点 s,使 s 指向图 G 中的所有顶点都建立连接,设新的图为 G’;
  2. 对图 G’ 中顶点 s 使用 Bellman-Ford 算法计算单源最短路径,得到结果 h[] = {h[0], h[1], … h[V-1]};
  3. 对原图 G 中的所有边进行 “re-weight”,即对于每个边 (u, v),其新的权值为 w(u, v) + (h[u] - h[v]);
  4. 移除新增的顶点 s,对每个顶点运行 Dijkstra 算法求得最短路径;

所以我们的关键是新建一个图利用Bellman-ford的值得到h数组的值,再利用w(u, v) = w(u, v) + (h[u] - h[v]).计算u和v的“re-weight”

我们来看伪代码(取自算法导论)
在这里插入图片描述
C++代码实现:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<string>
#include<stack>
#include<queue>
#include<cstring>
#include<map>
#include<iterator>
#include<list>
#include<set>
#include<functional>
#include<memory.h>

using namespace std;

typedef pair<int,int> pll;   //first代表权值,second代表顶点序号。
const int maxn=105;//顶点最大数
const int M=1e4;//边最大数
const int inf=0x3f3f3f3f;
int n,m;//顶点数与边数。
int cnt[maxn];//cnt[i]记录的是i顶点的入队次数。若大于n,则必然存在负环。
typedef struct Edge{
	int to;//边的终端节点
	int w;//边权值。
	Edge(int to,int w):to(to),w(w){}//构造函数
}Edge;
vector<Edge> graph[maxn];//用vector模拟邻接表
vector<Edge> graph_temp[maxn];//临时存储新图,为了得到h[maxn]的值。
int dis[maxn];//存储所有顶点到源点的距离。
int h[maxn];//新顶点到其余顶点的最短路径
bool visited[maxn];//判断是否在队列中。
int result[maxn][maxn];//此时这个即为我们得到的最终最短路径。
void init(){
	memset(cnt,0,sizeof(cnt));
	memset(dis,inf,sizeof(dis));
	memset(visited,false,sizeof(visited));
	memset(h,0,sizeof(h));//一开始都是直接相连的,设为0,若有负边才能使得更新。
    memset(result,inf,sizeof(result));
}
int spfa(){
	//我们这里用队列优化的spfa算法。
	for(int i=0;i<=n;i++){
		graph_temp[n+1].push_back(Edge(i,0));//将新顶点与其余顶点之间的边权值都设为0.
	}
	queue<int> q;
	q.push(n+1);
	cnt[n+1]++;
	visited[n+1]=true;
	int temp;
	int flag=0;
	while(!q.empty()){
		temp=q.front();
		q.pop();
		visited[temp]=false;
		int v,w;
		int t=graph_temp[temp].size();//避免一直调用size()函数。
		for(int i=0;i<t;i++){
			v=graph_temp[temp][i].to;
			w=graph_temp[temp][i].w;
			if(h[v]>h[temp]+w){
				 h[v]=h[temp]+w;
				 if(!visited[v]){
					visited[v]=true;
					cnt[v]++;
					q.push(v);
					if(cnt[v]>n+1){flag=1;return flag;}
				 }
			}
		}
	}
	return flag;
}
void Dijkstra(int i){
	//i代表的是第几条边
	priority_queue<pll,vector<pll>,greater<pll> > q;
	result[i][i]=0;
	q.push(pll(0,i));
	pll temp;//临时变量
	while(!q.empty()){
		temp=q.top();
		q.pop();
		int u=temp.second;
		if(visited[u])continue;
		visited[u]=true;
		int t=graph[u].size();int v,w;
		for(int j=0;j<t;j++){
			v=graph[u][j].to;
			w=graph[u][j].w;
			if(!visited[v]&&result[i][v]>result[i][u]+w){
				result[i][v]=result[i][u]+w;
				q.push(pll(result[i][v],v));
			}
		}
	}
}
int main(){
	while(cin>>n>>m){
		int u,v,w;
		init();
		for(int i=0;i<m;i++){
			cin>>u>>v>>w;
		    graph[u].push_back(Edge(v,w));
		    graph_temp[u].push_back(Edge(v,w));
		}
		int flag=spfa();
		if(flag){cout<<"存在负环,请重新构建"<<endl;continue; }
		//若不存在负环,此时我们得到的就是一个h数组了,我们利用公式去更新原图中的边即可。
		//w(u, v) = w(u, v) + (h[u] - h[v])
		int t;
		for(int i=0;i<n;i++){
			t=graph[i].size();
			for(int j=0;j<t;j++){
				v=graph[i][j].to;
				graph[i][j].w=graph[i][j].to+(h[i]-h[v]);//i是边起点,v是边终点。
			}
		}
		//经过这趟双层for循环,得到的即是我们新的权值。
		for(int i=1;i<=n;i++){
			//对每个顶点使用一次Dijkstra算法。
			Dijkstra(i);
		}
		//我们得到的就是所有顶点之间的最短路径。
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				cout<<i<<"->"<<j<<"最短路径为"<<result[i][j]<<endl;
			}
		}
	}
	return 0;
}

我这里实现Johnson算法利用了Bellman-ford用队列优化的spfa算法和Dijkstra算法,有什么疑问都可以在评论区留言或私信我,愿能帮助到你。

猜你喜欢

转载自blog.csdn.net/hzf0701/article/details/107697261
今日推荐