## 定义

（还记得这些定义吗？在阅读下列内容之前，请务必了解 [图论基础](/graph/basic) 部分。）

- 路径
- 最短路
- 有向图中的最短路、无向图中的最短路
- 单源最短路、每对结点之间的最短路

## 性质

对于边权为正的图，任意两个结点之间的最短路，不会经过重复的结点。

对于边权为正的图，任意两个结点之间的最短路，不会经过重复的边。

对于边权为正的图，任意两个结点之间的最短路，任意一条的结点数不会超过 $n$，边数不会超过 $n-1$。

## Floyd 算法

是用来求任意两个结点之间的最短路的。

复杂度比较高，但是常数小，容易实现。（我会说只有三个 `for` 吗？）

适用于任何图，不管有向无向，边权正负，但是最短路必须存在。（不能有个负环）

### 实现

我们定义一个数组 `f[k][x][y]`，表示只允许经过结点 $1$ 到 $k$，结点 $x$ 到结点 $y$ 的最短路长度。

很显然，`f[n][x][y]` 就是结点 $x$ 到结点 $y$ 的最短路长度。

我们来考虑怎么求这个数组

`f[0][x][y]`：边权，或者 $0$，或者 $+\infty$ （`f[0][x][x]` 什么时候应该是 $+\infty$？）

`f[k][x][y] = min(f[k-1][x][y], f[k-1][x][k]+f[k-1][k][y])`

上面两行都显然是对的，然而这个做法空间是 $O(N^3)$。

但我们发现数组的第一维是没有用的，于是可以直接改成 `f[x][y] = min(f[x][y], f[x][k]+f[k][y])`，

```c++
for (k = 1; k <= n; k++) {
	for (i = 1; i <= n; i++) {
		for (j = 1; j <= n; j++) {
			f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
		}
	}
}
```

时间复杂度是 $O(N^3)$，空间复杂度是 $O(N^2)$。

### 应用

???+ question "给一个正权无向图，找一个最小权值和的环"
    首先这一定是一个简单环。

    想一想这个环是怎么构成的。

    考虑环上编号最大的结点 u。

    `f[u-1][x][y]` 和 (u,x), (u,y)共同构成了环。

    在Floyd的过程中枚举u，计算这个和的最小值即可。

    $O(n^3)$。

???+question "已知一个有向图中任意两点之间是否有连边，要求判断任意两点是否联通"

    该问题即是求**图的传递闭包**。

    我们只需要按照 Floyd 的过程，逐个加入点判断一下。

    只是此时的边的边权变为 $1/0$， 而取 $\min$ 变成了**与**运算。

    再进一步用 bitset 优化，复杂度可以到 $O(\frac{n^3}{w})$。

    ```c++
    //std::bitset<SIZE> f[SIZE];
    for (k = 1; k <= n; k++)
    	for (i = 1; i <= n; i++)
    	    if(f[i][k]) f[i] = f[i] & f[k];
    ```

* * *

## Bellman-Ford 算法

一种基于松弛（relax）操作的最短路算法。

支持负权。

能找到某个结点出发到所有结点的最短路，或者报告某些最短路不存在。

在国内 OI 界，你可能听说过的 “SPFA”，就是 Bellman-Ford 算法的一种实现。（优化）

### 实现

假设结点为 $S$。

先定义 $dist(u)$ 为 $S$ 到 $u$ （当前）的最短路径长度。

$relax(u,v)$: $dist(v) = min(dist(v), dist(u) + edge\_len(u, v))$

$relax$ 是从哪里来的呢？

三角形不等式: $dist(v) \leq dist(u) + edge\_len(u, v)$。

证明：反证法，如果不满足，那么可以用 $relax$ 操作来更新 $dist(v)$ 的值。

Bellman-Ford 算法如下：

```text
while (1) for each edge(u, v) relax(u, v);
```

当一次循环中没有 $relax$ 操作成功时停止。

每次循环是 $O(m)$ 的，那么最多会循环多少次呢？

答案是 $\infty$！（如果有一个 $S$ 能走到的负环就会这样）

但是此时某些结点的最短路不存在。

我们考虑最短路存在的时候。

由于一次 $relax$ 会使（被 $relax$ 的）最短路的边数至少 $+1$，而最短路的边数最多为 $n-1$。

所以最多（连续）$relax$ $n-1$ 次……（$relax$ 一定是环环相扣的，不然之前就能被 $relax$ 掉）

所以最多循环 $n-1$ 次。

总时间复杂度 $O(NM)$。 **（对于最短路存在的图）**

```text
relax(u, v) {
	dist[v] = min(dist[v], dist[u] + edge_len(u, v));
}
for (i = 1; i <= n; i++) {
	dist[i] = edge_len(S, i);
}
for (i = 1; i < n; i++) {
	for each edge(u, v) {
		relax(u, v);
	}
}
```

注：这里的 $edge\_len(u, v)$ 表示边的权值，如果该边不存在则为 $+\infty$，$u=v$ 则为 $0$。

### 应用

给一张有向图，问是否存在负权环。

做法很简单，跑 Bellman-Ford 算法，如果有个点被 $relax$ 成功了 $n$ 次，那么就一定存在。

如果 $n-1$ 次之内算法结束了，就一定不存在。

### 优化 SPFA

Shortest Path Faster Algorithm

很多时候我们并不需要那么多无用的 $relax$ 操作。

很显然，只有上一次被 $relax$ 的结点，所连接的边，才有可能引起下一次的 $relax$。

那么我们用队列来维护 “哪些结点可能会引起 $relax$”，就能只访问必要的边了。

```text
q = new queue();
q.push(S);
in_queue[S] = true;
while (!q.empty()) {
	u = q.pop();
	in_queue[u] = false;
	for each edge(u, v) {
		if (relax(u, v) && !in_queue[v]) {
			q.push(v);
			in_queue[v] = true;
		}
	}
}
```

SPFA 的时间复杂度为 $O(kM)~ (k\approx 2)$ （玄学），但 **理论上界** 为 $O(NM)$，精心设计的稠密图可以随便卡掉 SPFA，所以考试时谨慎使用  （NOI 2018 中很多选手的 SPFA 被卡掉了）。

#### SPFA 的优化之 SLF

Small Label First

即在新元素加入队列时，如果队首元素权值大于新元素权值，那么就把新元素加入队首，否则依然加入队尾。

该优化在确实在一些图上有显著效果，其复杂度也有保证，但是如果有负权边的话，可以直接卡到指数级。

* * *

## Dijkstra 算法

Dijkstra 是个人名（荷兰姓氏）。

IPA: /ˈdikstrɑ/ 或 /ˈdɛikstrɑ/。

这种算法只适用于非负权图，但是时间复杂度非常优秀。

也是用来求单源最短路径的算法。

### 实现

主要思想是，将结点分成两个集合：已确定最短路长度的，未确定的。

一开始第一个集合里只有 $S$。

然后重复这些操作：

（1）$relax$ 那些刚刚被加入第一个集合的结点的所有出边。

（2）从第二个集合中，选取一个最短路长度最小的结点，移到第一个集合中。

直到第二个集合为空，算法结束。

时间复杂度：只用分析集合操作，$n$ 次 delete-min，$m$ 次 decrease-key。

如果用暴力： $O(n^2 + m)$。

如果用堆：$O((n+m) \log m)$。

如果用线段树（ZKW 线段树）：$(O(n+m)\log n)$

如果用 Fibonacci 堆： $O(n \log n + m)$（这就是为啥优秀了）。

等等，还没说正确性呢！

分两步证明：先证明任何时候第一个集合中的元素的 $dist$ 一定不大于第二个集合中的。

再证明第一个集合中的元素的最短路已经确定。

第一步，一开始时成立（基础），在每一步中，加入集合的元素一定是最大值，且是另一边最小值，$relax$ 又是加上非负数，所以仍然成立。（归纳） （利用非负权值的性质）

第二步，考虑每次加进来的结点，到他的最短路，上一步必然是第一个集合中的元素（否则他不会是第二个集合中的最小值，而且有第一步的性质），又因为第一个集合已经全部 $relax$ 过了，所以最短路显然确定了。

```text
H = new heap();
H.insert(S, 0);
dist[S] = 0;
for (i = 1; i <= n; i++) {
	u = H.delete_min();
	for each edge(u, v) {
		if (relax(u, v)) {
			H.decrease_key(v, dist[v]);
		}
	}
}
```

* * *

## 不同方法的比较

| Floyd      | Bellman-Ford | Dijkstra         |
| ---------- | ------------ | ---------------- |
| 每对结点之间的最短路 | 单源最短路        | 单源最短路            |
| 没有负环的图     | 任意图          | 非负权图             |
| $O(N^3)$   | $O(NM)$      | $O((N+M)\log M)$ |
