---
# System prepended metadata

title: All pairs shortest path

---

# 多源最短路徑(All pairs shortest paths/ multiple source shortest paths)

在Dijkstra後，應該了解了圖論的基本內容，所以就能夠繼續延伸幾個常見的圖論相關演算法。
更準確來說Dijkstra是<span style="color:red;">單源最短路徑</span>，也就是說每次使用Dijkstra，通常都是針對<span style="color:red;">某個起點</span>，那如果我們想要一次"處理多個起點和終點"呢？

所以可以簡單歸類為以下幾種

1. single source shortest path without negative cycle
2. multiple source shortest path without negative cycle (All pairs)
3. single source shortest path with negative cycle

那為甚麼沒有"全對全"有負環？

## 核心概念只有五行的演算法 - Floyd-Warshall 演算法

![image](https://hackmd.io/_uploads/rJxzBVwOgx.png)
我們現在要找的是任意兩個點之間的最短路徑，也稱作「多源最短路徑」。
先將圖的資訊存到二維矩陣中。
|   | 1  | 2  | 3  | 4  |
|---|----|----|----|----|
| 1 | 0  | 2  | 6  | 4  |
| 2 | ∞  | 0  | 3  | ∞  |
| 3 | 7  | ∞  | 0  | 1  |
| 4 | 5  | ∞  | 12 | 0  |

而這邊就要提到一個很重要的概念-<span style="color:red;">鬆弛</span>了
如果我們要求任意兩個點的最短距離，但有些點不能直達，勢必要有經過中間點才行。
我們來看看以下的分析：
```
4 → 3　為　12
4 → 1 → 3　為　11
4 → 1 → 2 → 3　為　10
```
- 這表示當兩點之間沒有經過第三點時，兩點間的初始距離就是最短路徑。
- 但若有經過中轉，可能不只一個，能讓總路徑變得更短。
我們只要透過多次的中轉，和初始的路徑比較，如果能讓兩點之間的路徑變短，就更新。好比 4 到 3 的原始距離為 12，4 到 1 的距離為 5，加上 1 到 3 的距離為 6 共 11，比原始 12 小，我們就讓二為矩陣中 4 到 3 的距離更新為 11 。因為我們只看最短路徑，不討論最少的中轉次數，所以可以這麼做。

所以只要「逐步嘗試把每個點 k 當作中繼點，看看經過它會不會更短」
在嘗試全部點後，得出的肯定就是最短路徑。

先來看以下程式碼
```clike
FloydWarshallAlgorithm(){
  for k in 0 to n - 1{
    for i in 0 to n - 1{
      for j in 0 to n - 1{
        if (distance[i][j] > distance[i][k] + distance[k][j]){
          distance[i][j] = distance[i][k] + distance[k][j]
}}}}}
```
最外層就表示
- 表示「現在嘗試的中繼點」
- 第 k 次迭代後，代表我們已經考慮過 <span style="color:red;"> 0 ~ k </span> 這些點，看看能不能經過它們讓距離變短。
我們就來練習看看吧
[Leetcode 1334. Find the City With the Smallest Number of Neighbors at a Threshold Distance](https://leetcode.com/problems/find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/description/)

```CPP
class Solution {
public:
    int findTheCity(int n, vector<vector<int>>& edges, int distanceThreshold) {
        vector<vector<int>> dist(n, vector<int>(n, INT_MAX));
        // 自己到自己的距離
        for (int i = 0; i < n; i++) dist[i][i] = ; // TODO
        for (int i = 0; i < edges.size(); i++) {
            int u = edges[i][0], v = edges[i][1], w = edges[i][2];
            dist[u][v] = ; // TODO
            dist[v][u] = ; // TODO
        }
        
        // Floyd–Warshall
        for (int k = 0; k < n; k++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    // 如果使用中繼點的路線比目前的還小，就要更新
                    if () { // TODO
                    }
                }
            }
        }
        
        /*
        現在你已經有了所有的最短路徑矩陣了，只要遍歷全部的起點i，
        計算每個起點在distanceThreshold能達到的城市數量，更新
        最小值就能返回了
        */
        
        int bestCity = -1, bestCnt = INT_MAX;
        
        // TODO
        
        return bestCity;
    }
};

```

課後練習
[Leetcode 1462. Course Schedule IV](https://leetcode.com/problems/course-schedule-iv/description/?envType=problem-list-v2&envId=9idenloe)



## Bellman-Ford Algorithm
然而Dijkstra不是萬能的，在處理負邊時就會遇到問題，我們來思考以下圖片如果用Dijkstra會怎麼更新，假設從0開始遍歷所有點
![image](https://hackmd.io/_uploads/rkX7H3tFxl.png)
```javascript
Dijkstra(G, w, s):
    # G: graph, w(u,v): edge weight, s: source
    for each vertex v in G.V:
        dist[v] ← ∞
        prev[v] ← NIL
    dist[s] ← 0

    Q ← all vertices in G     # min-priority queue (by dist)

    while Q is not empty:
        u ← Extract-Min(Q)    # vertex with smallest dist
        for each neighbor v of u:
            if dist[v] > dist[u] + w(u, v):
                dist[v] ← dist[u] + w(u, v)
                prev[v] ← u
                Decrease-Key(Q, v, dist[v])

    return dist[], prev[]
```
首先一開始會將所有點都設為無窮大
| 節點        | 0 | 1 | 2 
|-------------|---|---|---
| **Step 0**  | 0 | ∞ | ∞ 

接著Dijkstra更新完這個點的所有邊後，每次都是去遍歷下一個距離最小的點，所以下一個會是從2出發
| 節點        | 0 | 1 | 2 
|-------------|---|---|---
| **Step 0**  | 0 | 7 | 5

由於2沒有相鄰節點因此不更新
| 節點        | 0 | 1 | 2 
|-------------|---|---|---
| **Step 0**  | 0 | 7 | 5

![image](https://hackmd.io/_uploads/SykCMhtYge.png)
而bellman-ford algorithm如下
![image](https://hackmd.io/_uploads/BktJfTtKel.png)
![image](https://hackmd.io/_uploads/r1v7GTtYex.png)
![image](https://hackmd.io/_uploads/rJ4UMaYKge.png)
