全源最短路径
P5905 【模板】Johnson 全源最短路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
Johnson 和 Floyd 一样,是一种能求出无负环图上任意两点间最短路径的算法。
1,算法概述
任意两点间的最短路可以通过枚举起点,跑 n 次 Bellman-Ford 算法解决,时间复杂度是 O()的,也可以直接用 Floyd 算法解决,时间复杂度为 O().
堆优化的 Dijkstra 算法求单源最短路径的时间复杂度比 Bellman-Ford 更优,如果枚举起点,跑 n 次 Dijkstra 算法,就可以在 O().
但 Dijkstra 算法不能正确求解带负权边的最短路,因此我们需要对原图上的边进行预处理,确保所有边的边权均非负。
一种容易想到的方法是给所有边的边权同时加上一个正数 x,从而让所有边的边权均非负。如果新图上起点到终点的最短路经过了 k 条边,则将最短路减去即可得到实际最短路。
但这样的方法是错误的。考虑下图:
Johnson 算法则通过另外一种方法来给每条边重新标注边权。
我们新建一个虚拟节点(在这里我们就设它的编号为 0 )。从这个点向其他所有点连一条边权为 0 的边。
接下来用 Bellman-Ford 算法求出从 0 号点到其他所有点的最短路,记为.
假如存在一条从 u 点到 v 点,边权为 w的边,则我们将该边的边权重新设置为 。
接下来以每个点为起点,跑 n 轮 Dijkstra 算法即可求出任意两点间的最短路了。
容易看出,该算法的时间复杂度是O().
2.正确性证明
为什么这样重新标注边权的方式是正确的呢?
在讨论这个问题之前,我们先讨论一个物理概念——势能。
诸如重力势能,电势能这样的势能都有一个特点,势能的变化量只和起点和终点的相对位置有关,而与起点到终点所走的路径无关。
势能还有一个特点,势能的绝对值往往取决于设置的零势能点,但无论将零势能点设置在哪里,两点间势能的差值是一定的。
接下来回到正题。
代码
因为要跑bellman—ford,所以要记录边,虚构一个点0,使0到其他所有点的距离为0,
跑bellman—ford
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
cin >> a[i].u >> a[i].v >> a[i].power;
}
int Bellman()
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (dis[a[j].v] > dis[a[j].u] + a[j].power)
dis[a[j].v] = dis[a[j].u] + a[j].power;
}
}
int judge = 1;//判断是否有负环
for (int j = 1; j <= m; j++)
{
if (dis[a[j].v] > dis[a[j].u] + a[j].power)
{
judge = 0;
break;
}
}
return judge;
}
2,修改每条边的权值,并将源点0到其他的点的距离记录下来,后面还会用到
for (int i = 1; i <= m; i++)
{
a[i].power = a[i].power + dis[a[i].u] - dis[a[i].v];
}
for (int i = 1; i <= n; i++)
dis1[i] = dis[i];
3,构建堆,跑n遍dijkstra
for (int i = 1; i <= m; i++)
{
p.u = a[i].u;//没必要写这个
p.v = a[i].v; p.power = a[i].power;
mp[a[i].u].push_back(p);
}
for (int i = 1; i <= n; i++)
{
dijis(i);
}
void dijis(int x)
{
memset(book, 0, sizeof(book));
for (int i = 1; i <= n; i++)
dis[i] = Max;
dis[x] = 0;
priority_queue<node> q;
p.u = x; p.v = x;
p.power = 0;
q.push(p);
while (!q.empty())
{
node now = q.top(); q.pop();
if (book[now.v] == 1)
continue;
int y = now.v;
book[y] = 1;
int len = mp[y].size();
for (int i = 0; i < len; i++)
{
if (dis[mp[y][i].v] > dis[y] + mp[y][i].power)
{
dis[mp[y][i].v] = dis[y] + mp[y][i].power;
p.u = y; p.v = mp[y][i].v;
p.power = dis[mp[y][i].v];
q.push(p);
}
}
}
long long ans = 0;
//如果dis[i]==无穷时,说明节点x无法到达节点i,
//此时不需要复原dis[i]
for (int i = 1; i <= n; i++)
{
if(dis[i]!=Max)
dis[i] = dis[i] - dis1[x] + dis1[i];
}
for (int i = 1; i <= n; i++)
{
ans += i * dis[i];
}
cout << ans << endl;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话