Johnson全源最短路
首先考虑求全源最短路的几种方法:
- Floyd:时间复杂度\(O(n^3)\),可以处理负权边,但不能处理负环,而且速度很慢。
- Bellman-Ford:以每个点为源点做一次Bellman-Ford,时间复杂度\(O(n^2m)\),可以处理负权边,可以处理负环,但好像比Floyd还慢?
- dijkstra:以每个点为源点做一次dijkstra,时间复杂度\(O(nmlogm)\),不能处理负权边,但比前面两个快多了。
好像……只有dijkstra还有希望?但负权边处理不了真是很棘手啊。
一种方法是让每条边都加上一个数\(x\)使得边权为正,但考虑下图:

\(1\)到\(2\)的最短路应为:\(1 -> 3 -> 4 -> 2\),长度为\(-1\)。如果我们把每条边的边权都加上\(5\):

此时的最短路是:\(1 -> 5 -> 2\),就不是实际的最短路了,所以这种方法行不通
注:经本人研究,应该是两条路径进过的边的数量不同而导致的
接下来,就该 Johnson 登场啦!Johnson 其实就是用另一种方法标记边权啦。
首先来看看实现方法:我们新建一个虚拟结点(不妨设他的编号为0),由他向其他的所有结点都连一条边权为\(0\)的边,然后求0号节点为源点的单源最短路,存到一个\(h\)数组中。然后,让每条边的权值\(w\)变为\(w+h_u-h_v\),这里\(u\)和\(v\)分别为这条边的起点和终点。然后再以每个点为源点做 dijkstra 就OK了。
Q:那这么说,Dijkstra 也可以求出负权图(无负环)的单源最短路径了?
A:没错。但是预处理要跑一遍 Bellman-Ford,还不如直接用 Bellman-Ford 呢。
如何证明这是正确的呢?
首先,从\(s\)到\(t\)的路径中随便取出一条:
\]
则这条路径的长度为:
\]
简化后得到:
\]
可以发现,不管走哪条路径,最后都是\(+h_s-h_t\),而\(h_s\)和\(h_t\)又是不变的,所以最终得到的最短路径还是原来的最短路径。
到这里已经证明一半了,接下来要证明得到的边权非负,必须要无负权边才能使 dijkstra 跑出来的结果正确。根据三角形不等式(就是那个三角形里任意两条边的长度之和大于等于另一条边的长度),新图上的任意一条边\((u,v)\)上的两点满足:\(h_v \le w_{u,v}+h_u\),则新边的边权\(w_{u,v}+h_u-h_v \ge 0\)。所以新图的边权非负。
正确性证明就是这个亚子。
代码实现(注意处理精度问题,该开ll的时候开ll):
#include<cstdio>
#include<queue>
#define MAXN 5005
#define MAXM 10005
#define INF 1e9
using namespace std;
int n,m;
int vis[MAXN];
long long h[MAXN],dis[MAXN];
bool f[MAXN];
struct graph
{
int tot;
int hd[MAXN];
int nxt[MAXM],to[MAXM],dt[MAXM];
void add(int x,int y,int w)
{
tot++;
nxt[tot]=hd[x];
hd[x]=tot;
to[tot]=y;
dt[tot]=w;
return ;
}
}g;//链式前向星
bool SPFA(int s)//这里用了Bellman-Ford的队列优化
{
queue<int>q;
for(int i=1;i<=n;++i) h[i]=INF,f[i]=false;
h[s]=0;
f[s]=true;
q.push(s);
while(!q.empty())
{
int xx=q.front();
q.pop();
f[xx]=false;
for(int i=g.hd[xx];i;i=g.nxt[i])
if(h[g.to[i]]>h[xx]+g.dt[i])
{
h[g.to[i]]=h[xx]+g.dt[i];
if(!f[g.to[i]])
{
if(++vis[g.to[i]]>=n) return false;//注意在有重边的情况下要记录入队次数而不是松弛次数
f[g.to[i]]=true,q.push(g.to[i]);
}
}
}
return true;
}
void dijkstra(int s)
{
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
for(int i=1;i<=n;i++) dis[i]=INF,f[i]=false;
q.push(make_pair(0,s));
dis[s]=0;
while(!q.empty())
{
int xx=q.top().second;
q.pop();
if(!f[xx])
{
f[xx]=true;
for(int i=g.hd[xx];i;i=g.nxt[i])
if(dis[g.to[i]]>dis[xx]+g.dt[i])
{
dis[g.to[i]]=dis[xx]+g.dt[i];
if(!f[g.to[i]])
q.push(make_pair(dis[g.to[i]],g.to[i]));
}
}
}
return ;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
g.add(u,v,w);
}
for(int i=1;i<=n;i++) g.add(0,i,0);//建虚拟节点0并且往其他的点都连一条边权为0的边
if(!SPFA(0))//求h的同时也判了负环
{
printf("-1");
return 0;
}
for(int u=1;u<=n;u++)
for(int i=g.hd[u];i;i=g.nxt[i])
g.dt[i]+=h[u]-h[g.to[i]];//求新边的边权
for(int i=1;i<=n;i++)
{
dijkstra(i);//以每个点为源点做一遍dijkstra
long long ans=0;
for(int j=1;j<=n;j++)//记录答案
if(dis[j]==INF) ans+=1ll*j*INF;
else ans+=1ll*j*(dis[j]+(h[j]-h[i]));
printf("%lld\n",ans);
}
return 0;
}
最后安利一发博客
Johnson全源最短路的更多相关文章
- 【学习笔记】 Johnson 全源最短路
前置扯淡 一年多前学的最短路,当时就会了几个名词的拼写,啥也没想过 几个月之前,听说了"全源最短路"这个东西,当时也没说学一下,现在补一下(感觉实在是没啥用) 介绍 由于\(spf ...
- Johnson 全源最短路
学这个是为了支持在带负权值的图上跑 Dijkstra. 为了这个我们要考虑把负的权值搞正. 那么先把我们先人已经得到的结论摆出来.我们考虑先用 SPFA 对着一个满足三角形不等式的图跑一次最短路,具体 ...
- Johnson 全源最短路径算法学习笔记
Johnson 全源最短路径算法学习笔记 如果你希望得到带互动的极简文字体验,请点这里 我们来学习johnson Johnson 算法是一种在边加权有向图中找到所有顶点对之间最短路径的方法.它允许一些 ...
- Johnson 全源最短路径算法
解决单源最短路径问题(Single Source Shortest Paths Problem)的算法包括: Dijkstra 单源最短路径算法:时间复杂度为 O(E + VlogV),要求权值非负: ...
- 模板C++ 03图论算法 2最短路之全源最短路(Floyd)
3.2最短路之全源最短路(Floyd) 这个算法用于求所有点对的最短距离.比调用n次SPFA的优点在于代码简单,时间复杂度为O(n^3).[无法计算含有负环的图] 依次扫描每一点(k),并以该点作为中 ...
- Johnson算法:多源最短路算法
Johnson算法 请不要轻易点击标题 一个可以在有负边的图上使用的多源最短路算法 时间复杂度\(O(n \cdot m \cdot log \ m+n \cdot m)\) 空间复杂度\(O(n+m ...
- Floyd-Warshall 全源最短路径算法
Floyd-Warshall 算法采用动态规划方案来解决在一个有向图 G = (V, E) 上每对顶点间的最短路径问题,即全源最短路径问题(All-Pairs Shortest Paths Probl ...
- 【算法】单源最短路——Dijkstra
对于固定起点的最短路算法,我们称之为单源最短路算法.单源最短路算法很多,最常见的就是dijkstra算法. dijkstra主要用的是一种贪心的思想,就是说如果i...s...t...j是最短路,那么 ...
- 图论:Floyd-多源最短路、无向图最小环
在最短路问题中,如果我们面对的是稠密图(十分稠密的那种,比如说全连接图),计算多源最短路的时候,Floyd算法才能充分发挥它的优势,彻彻底底打败SPFA和Dijkstra 在别的最短路问题中都不推荐使 ...
随机推荐
- 推荐:实现RTSP/RTMP/HLS/HTTP协议的轻量级流媒体框架,支持大并发连接请求
推荐一个比较好用的流媒体服务开源代码: ZLMediaKit: 实现RTSP/RTMP/HLS/HTTP协议的轻量级流媒体框架,支持大并发连接请求 https://gitee.com/xiahcu/Z ...
- Linux 文件IO简单实例
目录 简述 代码 编译运行 简述 Linux下的所有资源都被抽象为文件,所以对所有资源的访问都是以设备文件的形式访问,设备文件的操作主要包括:打开.关闭.读.写.控制.修改属性等.下面的示例代码主要是 ...
- 移动端与Web端疫情数据展示
1.题目要求 2.整体思想 首先是在前两阶段已经完成的echarts可视化.利用Jsoup爬取疫情数据基础上来进行调用与完善.大致思想是在Android Studio上完成交互去调用ecplise中的 ...
- 一文搞定Python正则表达式
本文对正则表达式和 Python 中的 re 模块进行详细讲解 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知 ...
- Spring Boot 数据缓存 - EhCache
EhCache 集成 EhCache 是一个纯 Java 的进程内缓存框架,具有快速.精干等特点,是 Hibernate 中默认的 CacheProvider. 在 Spring Boot 中集成 E ...
- kubeadm部署1.17.3[基于Ubuntu18.04]
基于 Ubuntu18.04 使用 kubeadm 部署Kubernetes 1.17.3 高可用集群 环境 所有节点初始化 # cat <<EOF>> /etc/hosts ...
- 咕咕咕清单(SCOI2020前)
本篇博客已停更 本篇博客已停更 本篇博客已停更 吐槽区: 2020.04.15: 从今天起我做过的题目都记录一下,想不想写题解就另说了 2020.04.17: 写了两天之后真实的发现这是博主的摸鱼日记 ...
- Java基础—字符串
事实上,Java是没有内置的字符串类型的,而是在标准Java类库中提供了一个预定义类String.每个用双引号括起来的字符串都是String类的一个实例: String str = "&qu ...
- Android开发进程0.1 轮播图 Scrollview Fragment
轮播图的实现 轮播图通过banner可以较为便捷的实现 1.添加本地依赖,在dependence中搜索相关依赖 2.添加banner的view组件 3.创建适配器GlideImageLoader ex ...
- troubleshoot之:使用JFR解决内存泄露
目录 简介 一个内存泄露的例子 使用JFR和JMC来分析内存泄露 OldObjectSample 总结 简介 虽然java有自动化的GC,但是还会有内存泄露的情况.当然java中的内存泄露跟C++中的 ...