---
# System prepended metadata

title: NYCU PCCA 2022 Winter Camp 0127

---

# NYCU PCCA 2022 Winter Camp 0127
## [圖論](https://www.youtube.com/watch?v=NnM6tCJ_7VQ&list=PLtRlodjJ-YdIizrpGnftvH7hgJEQN7j9E&index=7)
今天的內容只有這個 影片大約2小時

### 圖的介紹
圖 (graph) 是由點(圓圈)跟邊(線條)所成的結構
![](https://media.discordapp.net/attachments/936213144035016725/936213160766087188/unknown.png)

* 點 Node
    * 一個點可以連出去很多邊
    * 度數(degree)
    入度(x)：有多少點指向點 x 的數量
    出度(x)：點 x 向外連接點的數量
    * 點上有數字，稱為點權重
    ![](https://media.discordapp.net/attachments/936213144035016725/936256940479250492/unknown.png)

* 邊 Edge
    * 一條邊只能連到兩個點(相同或相異)
    * 邊上有數字，稱為邊權重
    ![](https://media.discordapp.net/attachments/936213144035016725/936273455362498560/unknown.png)
    * 無方向的邊叫無向邊
    有方向的邊叫有向邊
    ![](https://media.discordapp.net/attachments/936213144035016725/936273707142369340/unknown.png)
    
### 圖的存法
#### 1. 鄰接矩陣 Adjacency Matrix
當 A 連到 B 時，陣列中 `array[A][B] = 1, array[B][A] = 1`
![](https://media.discordapp.net/attachments/936213144035016725/936274508623523950/unknown.png)

##### 存無向邊帶權重
把原本連接的 1 換成權重大小
![](https://media.discordapp.net/attachments/936213144035016725/936275143456600124/unknown.png)

##### 存有向邊帶權重
假設 A 指向 B，則陣列中 `arr[A][B] = 權重, arr[B][A] = 0`
![](https://media.discordapp.net/attachments/936213144035016725/936275600086278224/unknown.png)

> code
```cpp=
const int vertex = 1000; // 假設點有 1000 個
int graph[vertex][vertex];
void addEdge(int u, int v, int w) {
    graph[u][v] = w;
    // 存無向邊記得加 graph[v][u] = w;
}
```

#### 2. 鄰接串列 Adjacency List
就每個點而言，存取和該點連接的所有其他點
![](https://media.discordapp.net/attachments/936213144035016725/936278706639470652/unknown.png)

##### 存無向邊帶權重
把點和權重一起存取
![](https://media.discordapp.net/attachments/936213144035016725/936279672977768518/unknown.png)

##### 存有向邊帶權重
假設 A 指向 B，在 A 的串列存 B 和權重，B 的串列則不會有 A 的資料
![](https://media.discordapp.net/attachments/936213144035016725/936281240489844766/unknown.png)

> code
```cpp=
const int vertex = 1000; // 假設點有 1000 個
struct edge {
    int v, w;
}
vector<edge> graph[vertex];
void addEdge(int u, int v, int w) {
    graph[u].push_back({v, w});
    // 存無向邊記得加 graph[v].push_back({u, w});
}
```

#### 3. 邊列表 Edge list
就一條邊，存取連接的兩點和權重
![](https://media.discordapp.net/attachments/936213144035016725/936437642743140402/unknown.png)

> code
```cpp=
struct edge {
    int u, v, w;
}
vector<edge> edgeList;
void addEdge(int u, int v, int w) {
    edgeList.push_back({u, v, w});
}
```

#### 存法比較
1. **鄰接串列 Adjacency list**
空間複雜度 O(V + E)
2. **鄰接矩陣 Adjacency matrix**
空間複雜度 O(V^2^)
3. **邊列表 Edge list**
空間複雜度 O(E)
:::success
競賽最常用： 鄰接串列
不過每個演算法適合的存取方式也不盡相同 選擇適合的存法是重要的第一步
:::

### 圖的搜索
#### 1. 深度優先搜索(DFS)
* **D**epth **F**irst **S**earch
* 像人在走迷宮
    * 先選一條路走
    * 走到死路退回來，重選一條路
* 重要的資訊
    * In Stamp 入戳章
    * Out Stamp 出戳章
    > 這兩個資訊可以來解決很多問題
* 大多用遞迴實作，也可以用 Stack
* 說明
▼ 從節點0開始DFS
![](https://media.discordapp.net/attachments/936213144035016725/936485278674223124/unknown.png)
▼ 選出節點0連出去的邊中還未走訪的點 (節點1)
![](https://media.discordapp.net/attachments/936213144035016725/936568833890717696/unknown.png)
▼ 選出節點1連出去的邊中還未走訪的點 (節點5)
![](https://media.discordapp.net/attachments/936213144035016725/936568698926420039/unknown.png)
▼ 以此類推，直到該節點連結的點皆被走訪
![](https://media.discordapp.net/attachments/936213144035016725/936572281499578408/unknown.png)
▼ 節點2連出去的邊都已經被走訪過了，所以節點2走訪完了，開始往回走
![](https://media.discordapp.net/attachments/936213144035016725/936574441238954004/unknown.png)
▼ 節點3也走訪完了，繼續往回走
![](https://media.discordapp.net/attachments/936213144035016725/936574805291978792/unknown.png)
▼ 節點5還有連接到節點4，但是節點4沒有被走訪，所以先去節點4
![](https://media.discordapp.net/attachments/936213144035016725/936577160389476352/unknown.png)
▼ 節點4也走訪完了，回到節點5
![](https://media.discordapp.net/attachments/936213144035016725/936577031578202122/unknown.png)
▼ 節點5也走訪完了，繼續往回走
![](https://media.discordapp.net/attachments/936213144035016725/936577800666751016/unknown.png)
▼ 節點0也走訪完了，DFS結束，取得所有點的 In stamp, Out stamp
![](https://media.discordapp.net/attachments/936213144035016725/936578146277408808/unknown.png)
* 分析
    * 如果使用鄰接串列
        * 每個邊跟點只會被掃過一次
        * 時間複雜度 O(V+E)
    * 如果使用鄰接矩陣
        * 每次拜訪每個點都要在花 O(V) 的時間掃過跟每個點是否有邊
        * 時間複雜度 O(V^2^)
::: info
實作DFS通常使用時間複雜度較低的鄰接串列
:::
* code
```cpp=
const int vertex = 1000;
int inStamp[vertex], outStamp[vertex], stamp;
vector<int> graph[vertex];
void init() {
    memset(inStamp, 0, sizeof(inStamp);
    memset(outStamp, 0, sizeof(outStamp);
    stamp = 1;
}
void dfs(int u) {
    inStamp = stamp++; // 紀錄in stamp
    for (int v : graph[u]) { // 尋遍所有相鄰的點
        if (inStamp[i] == 0) { // 如果in stamp為0就代表還沒拜訪過
            dfs(i); // 那就拜訪這個點
        }
    }
    outStamp = stamp++; // 紀錄out stamp
}
```

#### 2. 廣度優先搜索(BFS)
* **B**readth **F**irst **S**earch
* 概念和淹水相同
    * 從原點擴散出去
* 重要的資訊
    * level值(第幾層)
    * 無權重圖中，為該點距離起點的最短路徑
* 搭配 Queue 實作
* 說明
▼ 從節點0開始BFS
![](https://media.discordapp.net/attachments/936213144035016725/936652652996087877/unknown.png)
▼ 將節點0的level設成0，並丟入queue
![](https://media.discordapp.net/attachments/936213144035016725/936652554379595876/unknown.png)
▼ 從queue中取最前面的節點(0)，並pop掉該節點
![](https://media.discordapp.net/attachments/936213144035016725/936653044475650138/unknown.png)
▼ 將節點0相鄰且沒有被拜訪過的點丟進queue，並將該level值+1
![](https://media.discordapp.net/attachments/936213144035016725/936656554306990150/unknown.png)
▼ 從queue中取最前面的節點(1)，並pop掉該節點
![](https://media.discordapp.net/attachments/936213144035016725/936664996740481054/unknown.png)
▼ 將節點1相鄰且沒有被拜訪過的點丟進queue，並將該level值+1
![](https://media.discordapp.net/attachments/936213144035016725/936665378950610944/unknown.png)
▼ 從queue中取最前面的節點(4)，並pop掉該節點
![](https://media.discordapp.net/attachments/936213144035016725/936665667250315294/unknown.png)
▼ 節點4沒有相鄰且沒被拜訪的點，所以不動作
![](https://media.discordapp.net/attachments/936213144035016725/936666750114730024/unknown.png)
▼ 重複以上動作，直到queue為空且每個點都走訪完畢，完成BFS。
![](https://media.discordapp.net/attachments/936213144035016725/936664337832112128/unknown.png)

* 分析
    * 如果使用鄰接串列
        * 每個邊跟點只會被掃過一次
        * 時間複雜度O(V+E)
    * 如果使用鄰接矩陣
        * 每次拜訪每個點都要在花O(V)的時間掃過跟每個點是否有邊
        * 時間複雜度O(V^2^)
::: info
實作BFS通常使用時間複雜度較低的鄰接串列
:::
* code
```cpp=
const int vertex = 1000;
int level[vertex];
vector<int> graph[vertex];
void init() {
    memset(level, -1, sizeof(level);
}
void bfs(int s) {
    queue<int> q;
    q,push(s); // 將起點丟到queue裡面
    level[s] = 0;
    while (q.size()) { // 直到queue是空的才停止
        int u = q.front(); // 拿出queue最前面的節點
        q.pop();
        for (int i : graph[u]) { // 尋遍所有和相鄰的點
            if (level[i] != -1) { // 拜訪過就不動作
                continue;
            }
            level[i] = level[u] + 1; // 更新level
            q.push(i);
        }
    }
}
```

#### 3. DFS & BFS
* **對圖最基礎的兩個操作**
大部分的演算法都為這兩種操作的組合
* **代價便宜**
如果用Adjaceny list，代價均為線性時間

### 樹
DFS和BFS做完之後，會把一張圖變成一棵樹
![](https://media.discordapp.net/attachments/936213144035016725/936786790050840596/unknown.png)

這是一棵仿真實世界的樹
![](https://media.discordapp.net/attachments/936213144035016725/936787587811639316/unknown.png)
但資工的樹通常是反過來的
![](https://media.discordapp.net/attachments/936213144035016725/936788016138162217/unknown.png)

#### 樹的一些重要名詞
* 根
位於樹的最頂端
![](https://media.discordapp.net/attachments/936213144035016725/936788469215268924/unknown.png)
* 葉子
位於樹的最底端
![](https://media.discordapp.net/attachments/936213144035016725/936788806902882314/unknown.png)
* 點和點關係
    * 祖先
    該點到根的路徑經過的點
    ![](https://media.discordapp.net/attachments/936213144035016725/936794912068886548/unknown.png)
    * 小孩和爸爸
    該點的上一層
    ![](https://media.discordapp.net/attachments/936213144035016725/936795332338130944/unknown.png)
    * 兄弟姊妹
    同一個爸爸
    ![](https://media.discordapp.net/attachments/936213144035016725/936795534923005962/unknown.png)
* 子樹
樹的某一部份切下來也會是一棵樹
![](https://media.discordapp.net/attachments/936213144035016725/936795748538941450/unknown.png)

#### 樹的一些重要性質
* n個點的樹僅有n-1條邊
![](https://media.discordapp.net/attachments/936213144035016725/936796197895692308/unknown.png)
* 任兩點僅有唯一一條路徑
![](https://media.discordapp.net/attachments/936213144035016725/936797359940177920/unknown.png)
* 找不到環
> 滿足任兩個性質，第三個就會自動滿足

#### 樹的問題
##### <font size = 3>子樹的size</font>
建立出每一個子樹他的size有多大
例如：
![](https://media.discordapp.net/attachments/936213144035016725/936802550437531668/unknown.png)
* 方法：一次DFS
▼ 先將每一個節點size值設成1
![](https://media.discordapp.net/attachments/936213144035016725/936919921638653962/unknown.png)
▼ 從根開始DFS
![](https://media.discordapp.net/attachments/936213144035016725/936924229667016724/unknown.png)
▼ 遍歷它的小孩們
![](https://media.discordapp.net/attachments/936213144035016725/936933282568884274/unknown.png)
▼ 往上的時候把小孩的值加給爸爸
![](https://media.discordapp.net/attachments/936213144035016725/936933675524816926/unknown.png)
![](https://media.discordapp.net/attachments/936213144035016725/937010443451514920/unknown.png)
![](https://media.discordapp.net/attachments/936213144035016725/937006130079608832/unknown.png)
▼ DFS結束後，就能得到答案了
![](https://media.discordapp.net/attachments/936213144035016725/937010936856842320/unknown.png)
* 複雜度分析：
過程只有一次DFS，所以時間複雜度 O(V+E)
* code
```cpp=
const int vertex = 1000;
vector<int> graph[vertex];
int size[vertex];
int dfs(int u, int parent) {
    size[u] = 1; // 初始化每個點的size
    for (int i : graph[u]) { // dfs每個節點
        if (i != parent) {
            size[u] += dfs(i, u); // 把小孩size加進來
        }
    }
    return size[u];
}
```

##### <font size = 3>最小生成樹</font>
:::success
簡報上的「樹」打成「樹」
交大國文就很差，因為交大資工可以不用看國文，所以講師國文很差滿正常的
:::
給定有權重的聯通圖找出一張子圖，在這張的子圖中選出權重總和最小的生成樹且必須包含所有節點。
例如下圖中綠色的部分，就是這個圖的最小生成樹：
![](https://media.discordapp.net/attachments/936213144035016725/937188728584146964/unknown.png)
* 方法: Kruskal's algorithm (有點greedy的味道)
將所有的邊依照權重由小到大排序
從最小開始，選擇不會形成環的邊，直到連接所有節點
![](https://media.discordapp.net/attachments/936213144035016725/937190319529807962/unknown.png)
▼ 用EdgeList方式將所有邊存下來
![](https://media.discordapp.net/attachments/936213144035016725/937190154408452116/unknown.png)
▼ 將Edgelist依權重由小到大排序，並從最小開始
![](https://media.discordapp.net/attachments/936213144035016725/937191809732476928/unknown.png)
▼ 將(1, 3)加入不會形成環
![](https://media.discordapp.net/attachments/936213144035016725/937193685458763836/unknown.png)
▼ 將(2, 3)加入不會形成環
![](https://media.discordapp.net/attachments/936213144035016725/937193611517370429/unknown.png)
▼ 同樣，將(4, 5)和(3, 4)加入也不會形成環
![](https://media.discordapp.net/attachments/936213144035016725/937204326470914068/unknown.png)
▼ 但是將(3, 5)加入會形成環，所以不加入
![](https://media.discordapp.net/attachments/936213144035016725/937210849599307776/unknown.png)
▼ 將(0, 1)加入不會形成環
![](https://media.discordapp.net/attachments/936213144035016725/937212734246899722/unknown.png)
將(1, 2)、(1, 4)、(0, 2)加入都會形成環，所以都不加入
所以最小生成樹的權重為15
:::warning
使用此方法能保證最小生成樹的權重，但是圖不一定是唯一的
:::
* 複雜度分析
sorting 所以的邊複雜度 O(ElogE)
判斷是否在同一個聯通塊中可以用disjoint set來判斷
總共時間複雜度 O(ElogE)
* code
```cpp=
struct edge {
    int u, v, w;
};
bool cmp(edge a, edge b) {
    return a.w < b.w;
}
vector<edge> edgeList;
int kruksal() {
    int weight = 0;
    sort(edgeList.begin(), edgelist.end(), cmp);
    for (auto i : edgeList) {
        if (merge(i.u, i.v)) { // 判斷u, v是否在同個聯通塊中
            weight += k.w;
        }
    }
    return weight;
}
```

##### <font size = 3>樹的直徑</font>
找出這棵樹中最長的那條路徑
如下圖：
![](https://media.discordapp.net/attachments/936213144035016725/937239863948873728/unknown.png)
* 方法：兩次BFS (還可以用DP做)
▼ 隨便對一個點為起點做BFS
![](https://media.discordapp.net/attachments/936213144035016725/937240589001449482/unknown.png)
▼ 找出離那點最遠的點
![](https://media.discordapp.net/attachments/936213144035016725/937240926080860190/unknown.png)
▼ 再以該點做一次BFS
![](https://media.discordapp.net/attachments/936213144035016725/937241289756389396/unknown.png)
▼ S和最遠的點的路徑就是樹直徑
![](https://media.discordapp.net/attachments/936213144035016725/937242687223005194/unknown.png)
* 複雜度分析
做兩次BFS，時間複雜度O(V+E)
* code
init(), bfs()詳細請回去看BFS的code
```cpp=
int getDiameter() {
    init(); // 初始化
    bfs(0); // 第一次bfs
    int maxLevel = 0, maxIndex = 0;
    for (int i = 0; i < vertex; i++) {
        if (maxLevel < level[i]) {
            maxLevel = level[i];
            maxIndex = i;
        }
    }
    init();
    bfs(maxIndex);
    for (int i = 0; i < vertex; i++) {
        if (maxLevel < level[i]) {
            maxLevel = level[i];
            maxIndex = i;
        }
    }
    return maxLevel;
}
```

### 有向無環圖 DAG
DAG = Directed Acyclic Graph
![](https://media.discordapp.net/attachments/936213144035016725/937249810401079306/unknown.png)
#### 拓樸排序 Topological Sort
找出一種在 DAG 上合理的排列順序
以這個DAG為例就是 a b e g h c d f
![](https://media.discordapp.net/attachments/936213144035016725/937249810401079306/unknown.png)
* 方法：BFS拔拔樂
每次摘掉 1 個 in-degree 為 0 的起點
將它鄰居的 in-degree 都減 1
如果遇到減完後 in-degree 變成 0 的就丟進 queue 裡面
![](https://media.discordapp.net/attachments/936213144035016725/937290415957241926/unknown.png)
▼ 先計算每個點的 in-degree
![](https://media.discordapp.net/attachments/936213144035016725/937293124705861642/unknown.png)
▼ 把 in-degree = 0 的點放入 queue (a, g)
![](https://media.discordapp.net/attachments/936213144035016725/937293499131392040/unknown.png)
![](https://media.discordapp.net/attachments/936213144035016725/937293786164363284/unknown.png)
▼ 將 queue.front() pop掉，並把pop掉的點放到 topo-sort 裡面
![](https://media.discordapp.net/attachments/936213144035016725/937294021758439444/unknown.png)
![](https://media.discordapp.net/attachments/936213144035016725/937294342983405588/unknown.png)
▼ 把與該點相鄰的點 in-degree - 1，如果 in-degree 變 0，就把那個點加入 queue
![](https://media.discordapp.net/attachments/936213144035016725/937294589885284362/unknown.png)
![](https://media.discordapp.net/attachments/936213144035016725/937295266585923584/unknown.png)
▼ 一直重複，最後會變成
![](https://media.discordapp.net/attachments/936213144035016725/937297848599449630/unknown.png)
▼ topo-sort 裡面就是答案
![](https://media.discordapp.net/attachments/936213144035016725/937298108839252039/unknown.png)
* code
```cpp=
const int MAXN = 1e3 + 5; // 1005
int n;
vector<int> G[MAXN];
int inDegree[MAXN];
void init() {
    memset(inDegree, 0, sizeof(inDegree));
}
void addEdge(int u, int v) {
    G[u].push_back(v);
    inDegree[v]++;
}
void bfsTopo() {
    queue<int> q;
    for (int i = 0; i < n; i++) {
        if (inDegree[i] == 0) { // 如果點的in-degree = 0
            q.push(i); // 把這個點丟進queue
        }
    }
    vector<int> topo;
    while (q.size()) { // queue還沒空就繼續做
        int u = q.front();
        q.pop();
        topo.push_back(u); // 把拔拔樂的點丟進拓樸排序
        for (auto &i : u) {
            inDegree[i]--; // 把和這個點相連的點in-degree - 1
            if (inDegree[i] == 0) { // 如果in-degree變成0
                q.push(i); // 把那個點丟進queue
            }
        }
    }
}
```

#### 經典問題
##### <font size = 3>DAG最長路</font>
在DAG中找出最長的路徑
* 方法：BFS拔拔樂
▼ 先將所有點的dist設為-1，in-degree為0的點則設為0
![](https://media.discordapp.net/attachments/936213144035016725/937989216384737320/unknown.png)
▼ 接著把dist = 0的點丟進queue
對和queue裡的點有連接的點做dist[x] = max(dist[x], dist[ parent[x] ] + 1)
並把它們加入queue
以 a 為例：
dist[b] = max(dist[b], dist[a] + 1)
dist[c] = max(dist[c], dist[a] + 1) (b, c會加入queue)
![](https://media.discordapp.net/attachments/936213144035016725/937990945843068978/unknown.png)
▼ 重複上述步驟，直到尋遍整個圖，就能找到dist最大的為最長路
![](https://media.discordapp.net/attachments/936213144035016725/938010982851153920/unknown.png)

##### <font size = 3>DAG最短路</font>
在DAG中給定一個點，找到該點對任何一點的最短距離
* 方法：BFS拔拔樂
▼ 假設a為起點，將a的dist設為0，其他點設為無限大，並把a加入queue
![](https://media.discordapp.net/attachments/936213144035016725/938014094265565194/unknown.png)
▼ 取queue最前面的點
對該點連接的點做dist[x] = min(dist[x], dist[ parent[x] ] + 1)
之後把有連接的點加入queue
![](https://media.discordapp.net/attachments/936213144035016725/938016368459141140/unknown.png)
▼ 重複上述步驟，直到尋遍整個圖
可以發現到下圖中g的dist是無限大，這表示從a點無法走到g
![](https://media.discordapp.net/attachments/936213144035016725/938017452095336488/unknown.png)
:::info
BFS拔拔樂可以適用於DAG各點權重不同的情況(包含最長&最短路)
:::

##### <font size = 3>一般圖呢？</font>
一般圖因為有環，所以不能使用BFS拔拔樂
( 環上每點的in-degree都不會是0 )
那該怎麼辦？
* relaxation操作
    * 原本已知最短路徑如下：
    ![](https://media.discordapp.net/attachments/936213144035016725/938079742752604261/unknown.png)
    * 找到一條更短的路，讓當前最短路更短了
        * dis[i]為從原點到 i 的當前最短路徑
        * dis[v] = min(dis[v], dis[u] + cost(w, v))
    ![](https://media.discordapp.net/attachments/936213144035016725/938081049039216640/unknown.png)
* Djikstra演算法
貪心性質，如果該節點距離是尚未被選取的點中最小的，那他就是最短路徑
我們可以透過這個最短路做relaxation，維護其他邊
▼ 假設0是起點，初始化dis陣列 (dis[i] = 起點到i的距離)
![](https://media.discordapp.net/attachments/936213144035016725/938269887669862462/unknown.png)
![](https://media.discordapp.net/attachments/936213144035016725/938270226418663454/unknown.png)
▼ 選出最小的 dis[i] (節點0)，並對他的鄰居做relaxation操作
![](https://media.discordapp.net/attachments/936213144035016725/938270764258459658/unknown.png)
![](https://media.discordapp.net/attachments/936213144035016725/938270875738857482/unknown.png)
▼ 選出最小的 dis[i] (節點1)，並對他的鄰居做relaxation操作
![](https://media.discordapp.net/attachments/936213144035016725/938279783027056650/unknown.png)
![](https://media.discordapp.net/attachments/936213144035016725/938343659181576192/unknown.png)
▼ 選出最小的 dis[i] (節點3)，並對他的鄰居做relaxation操作
![](https://media.discordapp.net/attachments/936213144035016725/938344466455085056/unknown.png)
![](https://media.discordapp.net/attachments/936213144035016725/938344588052144158/unknown.png)
▼ 選出最小的 dis[i] (節點2)，並對他的鄰居做relaxation操作
![](https://media.discordapp.net/attachments/936213144035016725/938347388337274930/unknown.png)
![](https://media.discordapp.net/attachments/936213144035016725/938348006166654987/unknown.png)
▼ 選出最小的 dis[i] (節點4)，沒有鄰居不動作
![](https://media.discordapp.net/attachments/936213144035016725/938348238946320434/unknown.png)
![](https://media.discordapp.net/attachments/936213144035016725/938348409281216532/unknown.png)
▼ 選出最小的 dis[i] (節點5)，沒有鄰居不動作
![](https://media.discordapp.net/attachments/936213144035016725/938348709291388958/unknown.png)
![](https://media.discordapp.net/attachments/936213144035016725/938348812253163530/unknown.png)
所有點都選到了，Dijkstra結束
    * 實作
        * 每次都挑選當前dis陣列中最小的節點
            :::spoiler 需要一個可以支援插入新東西並排好序的 (先想想看再點開看答案)
            priority queue
            :::
        * 複雜度分析
        最壞的情況每個點每個邊都需要被丟進 priority queue
        O((E+V)logV)
    * code
    ```cpp=
    struct edge {
        int v, w;
        bool operator <(const edge &cmp) const {
            return cmp.w < w; // 定義edge的排序方式(小到大)
        }
    };
    vector<edge> graph[1000];
    int dis[1000];
    void dijkstra(int s) {
        memset(dis, 0, sizeof(dis));
        priority_queue<edge> pq;
        pq.push({s, 0});
        while (pq.size()) {
            auto node = pq.front();
            pq.pop();
            if (dis[node.v] != -1) continue; // node.v在之前就更新過了
            dis[node.v] = node.w;
            for (auto k : graph[node.v]) { // 列舉所有相鄰的邊
                if (dis[k.v] == -1) { // relaxation操作
                    pq.push({k.v, k.w + node.w});
                }
            }
        }
    }
    ```

* Bellman-Ford 演算法
    * 每一回合都讓每條邊都relaxation一次
    * 做 n - 1 回合就完成單源點最短路
    * 適用於任何的圖

    用 Edge List 的方式存起來 (Adjacency List也可以，作法大同小異)，初始化dis陣列
![](https://media.discordapp.net/attachments/936213144035016725/938388259585818634/unknown.png)
對第一條邊做relaxation操作
![](https://media.discordapp.net/attachments/936213144035016725/938400426611572746/unknown.png)
對第二條邊做relaxation操作
![](https://media.discordapp.net/attachments/936213144035016725/938455313953218570/unknown.png)
對第三條邊做relaxation操作
![](https://media.discordapp.net/attachments/936213144035016725/938455515925717012/unknown.png)
對第四條邊做relaxation操作
![](https://media.discordapp.net/attachments/936213144035016725/938457466474209300/unknown.png)
對第五條邊做relaxation操作
![](https://media.discordapp.net/attachments/936213144035016725/938458165035548772/unknown.png)
對第六條邊做relaxation操作
![](https://media.discordapp.net/attachments/936213144035016725/938458364365647904/unknown.png)
對第七條邊做relaxation操作
![](https://media.discordapp.net/attachments/936213144035016725/938458802032898078/unknown.png)
對第八條邊做relaxation操作，完成第一回合
![](https://media.discordapp.net/attachments/936213144035016725/938459047093477437/unknown.png)
完成第 n - 1 回合之後，就能找到最短路
![](https://media.discordapp.net/attachments/936213144035016725/938460160068837406/unknown.png)
    * 為什麼最多要做要做 n - 1 次
        * n 個點的圖中的路徑最多經過 n 個點 (每個點都走到)
        * 每一次relaxation最多讓當前最短路徑多增加一個點
        * 因此每個點最多需要做 n - 1 次的relaxation才會形成 n 個點的路徑
    * 複雜度分析
        * 一次relaxation的cost為 O(1)
        * 每一回合都會做O(E)次 relaxation
        * 總共要做O(V - 1)回合
        
        **總共是 O((V - 1) * E * 1) = O(VE)**

    * code
```cpp=
const long long inf = 1e18;
cinst int maxn = 1000;
int n, dis[maxn];
vector<edge> edgeList; // 邊列表
void bellmanFord(int s) {
    for (int i = 0; i < maxn; i++) dis[i] = inf;
    dis[s] = 0;
    for (int i = 0; i < n - 1; i++) { // n - 1次
        for (auto e : edgeList) { // 每次都跑全部的邊
            dis[e.v] = min(dis[e.v], dis[e.u] + e.w); // relaxation操作
        }
    }
}
```

##### <font size = 3>負權重的一般圖呢？</font>
Dijkstra 不行 (貪心性質不會成立)
BellmanFord 不一定，因為：
* 負環
    * 找到一個環，他的總和是負數
    * 在有負環的圖中，多繞幾圈他會形成更短的路徑
    所以**不存在最短路徑**
![](https://media.discordapp.net/attachments/936213144035016725/938647217760243732/unknown.png)
:::info
若圖沒有出現可以到達的負環，BellmanFord依然可以照常運作。
:::
* 用 Bellman Ford 偵測負環
    * Bellman Ford 在沒有負環的圖做 n - 1 次的迭代就會收斂
    做 n - 1 次迭代還沒有收斂，表示有負環
    * 紀錄每個點被更新幾次，更新超過 n - 1 次代表這張圖有負環
* code
```cpp=
const long long inf = 1e18;
cinst int maxn = 1000;
int n, dis[maxn];
vector<edge> edgeList;
bool negativeCycle() {
    for (int i = 0; i < maxn; i++) dis[i] = 0;
    for (int i = 0; i < n; i++) { // 原本做n - 1次
        for (auto e : edgeList) {
            if (dist[e.v] > dist[e.u] + e.w) {
                dist[e.v] = dist[e.u] + e.w;
                if (i == n - 1) return true; // 如果n - 1次之後還有更新就代表有負環
            }
        }
    }
    return false;
}
```

##### <font size = 3>一般圖全點對最短路徑</font>
* 如果圖為正值權，那就做V次dijstra複雜度 O( V(E+V)logV )
* 如果圖為負值權，那就做V次bellman Ford複雜度 O( EV^2^ )
:::danger
<font size = 5>但但但！！！！！！</font>
如果圖為完全圖 E = V^2
正值權複雜度O(V^3lgV)
負值權為O(V^4)
:::
再看一下relaxation的式子dis[v] = min(dis[v], dis[k] + dis(k, v))
我們改寫一下式子讓dis[u][v]為u到v的最短距離
dis[u][v] = min(dis[u][v], dis[u][k] + dis(k, v))
有沒有發現這個**很像dp式！**
dp(k, u, v) 為利用前k個節點relaxation後的結果
**dp(k + 1, u, v) = min{dp(k, u, v), dp(k, u, k + 1) + dp(k, k + 1, v)}**
其實討論 k + 1 的時候只需要用到 k 的部分所以可以重複使用 dis 陣列！！！
這就是 ==floyd warshall 演算法==
* code
```cpp=
int vertex = 100;
int dis[vertex][vertex];
for (int i = 0; i < vertex; i++) {
    for (int j = 0; j < vertex; j++) {
        for (int k = 0; k < vertex; k++) {
            dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
        }
    }
}
```
* 複雜度分析 O(V^3^)

### 延伸題材
* 割點和橋
* 樹上LCA
* 各種連通分量
* 點雙連通、邊雙連通、強連通分量
* 配對問題
* 最大流與最小割
* 最大團與最大獨立集
* 一筆劃問題
* . . . . . .

### 課後練習
[Formula 1](https://oj.nctu.edu.tw/problems/1391/)
[Mad mobile phone gamer !](https://oj.nctu.edu.tw/problems/1393/)
[Deducting Weights](https://oj.nctu.edu.tw/problems/1396/)
[Drug Dealer](https://oj.nctu.edu.tw/problems/1392/)
[Building Highways](https://oj.nctu.edu.tw/problems/1389/)
[Time Machine Network](https://oj.nctu.edu.tw/problems/1397/)
[TIOJ 1509 . 地道問題](https://tioj.ck.tp.edu.tw/problems/1509)
[Problem - 938D - Codeforces](https://codeforces.com/problemset/problem/938/D)
[Problem - 1463E - Codeforces](https://codeforces.com/contest/1463/problem/E)