Johnson 全源最短路详解(小白向)

Part -1 前置算法

你需了解dijkstra算法与bellman_ford算法。

如果你想了解dijkstra,你可以去看程序员小灰的文章,C++代码我会在文章末尾给出(dijkstra,floyd,bellmen_ford和主角johnson)

如果你想了解bellman_ford,可以参考《算法笔记》中的bellman_ford讲解,或者百度一下

Part 1 起因

nlogn的堆优化dijkstra跑n次也要比n^3的floyd快。用dijkstra跑全源最短路应该不是一个坏主意。于是我询问了学长:

请教神犇

请教神犇2

询问得知,dijkstra处理不了权值为负数的边

为什么dijkstra处理不了负边?

算法笔记部分
而且,dijkstra无法判断负环

所以,只要我们将边权全部替换为正的,dijkstra就可以正常运行。

加大数让dijkstra处理负边

将图中每一项都加一个非常大的数。可以将负边换掉
但是这会使
答案错误
。举一个例子:

反例
如图,当我们加上1时,本来的最短路为 1->2->3->4,长度为3;加上数后明显从1->4是最短的,长度为5,原最短路长度为6。

Part 2 概述

原图

首先我们添加一个新节点,使这个节点与图上的每一个定点都联通,权值均为0。

第一步:加入节点

这时候,我们对新节点用bellman_ford求最短路(bellman_ford可以处理负边,堆优化的SPFA不能处理负边)。得到的结果非0即负,用一个数组h将结果存起来。

第二步:bellman-ford

这个时候,我们使用下面这个公式,来更新每一条边的边权。(w即为weight,权重。等号左边为新权值,等号右边为原权值+h[起点]-h[终点]

补丁:3->4的路径为-2+(-3-(-5)) = 0,少打了一个负号

妈咪叔做的latexlive生成的图片:公式

第三步:更新权值

此时图上已经没有负边了,我们可以放心大胆地dijkstra了。

针对每一个定点进行dijkstra,但是得到的距离表并不是原权值,而是经过bellman_ford的权值,所以我们需要进行换算

第四部:换算

之前是 + h(u) - h(v),现在反过来就是 + h(v) - h(u)了。

Part 3 正确性证明

算法导论地证明+注释

Part 4 题目&代码

dotcpp并没有针对这个算法的题目,于是这里给下算法对应的题目。(希望管理添加这道题目)

题目描述

给定一个包含 n 个结点和 m 条带权边的有向图,求对于每一个节点i的下面这个式子的结果。(dis(i, j)表示i到j的距离,节点编号从1开始一直到n)
魔鬼式子

注意:

  1. 边权可能为负,且图中可能存在重边和自环
  2. 数据卡spfa。
  3. 这题卡long long了,printf别忘了用lld。
输入数据

第 1 行:2 个整数 n,m,表示给定有向图的结点数量和有向边数量。

接下来 m 行:每行 3 个整数 u,v,w,表示有一条权值为 w 的有向边从编号为 u 的结点连向编号为 v 的结点。

输出数据

如果图中存在负环输出-1。

如果不存在负环:

对于每一个节点i输出上面那个式子。
如果不存在从i到j的路径,则路径长为10 ^ 9(1000000000),i到i距离为0.

输入输出样例
样例1

输入

5 7
1 2 4
1 4 10
2 3 7
4 5 3
4 2 -2
3 4 -3
5 3 4

输出

128
1000000072
999999978
1000000026
1000000014
样例2

输入

5 5
1 2 4
3 4 9
3 4 -3
4 5 3
5 3 -2

输出

-1
数据范围

n <= 3000,m <= 6000

代码

(由于数据过大,所以dis表必须开成全局变量,在函数内开不出来。)

//
// Created by Cat-shao on 2021/2/3.
//

#include <cstdio>
#include <deque>
#include <queue>
#include <cstring>
#include <climits>
using namespace std;

const long long INF = 1000000000;
const int MAX_N = 3100;

class edge {
   
public:
    int to;
    long long cost;
    edge() {
   
    }

    edge(int _to, long long _cost) {
   
        to = _to;
        cost = _cost;
    }

    bool operator < (const edge &e) const {
   
        return cost > e.cost;
    }
};

class dequeGraph {
   
public:
    deque<edge> vertex[MAX_N];
    int v;
    dequeGraph(int _v) {
   
        v = _v;
    }
    void insert(int from, int to, long long cost) {
   
        vertex[from].push_back(edge(to, cost));
    }

    void copy(dequeGraph *graph) {
   
        for (int i = 1; i <= v; ++i) {
   
            graph->vertex[i] = vertex[i];
        }
    }
};

void dij(dequeGraph *graph, long long dis[], int path[], int n, int s) {
   
    bool mark[MAX_N];
    priority_queue<edge> q;
    memset(dis, 
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值