# 圖論(Graph Theory)
圖論(Graph Theory)是探討關於圖(Graph)這個數學模型的理論
主要目的是找到如何解決有關於圖(Graph)的問題
以下為圖的範例([source](https://zh.wikipedia.org/zh-tw/%E5%9B%BE_(%E6%95%B0%E5%AD%A6)#/media/File:6n-graf.svg)):

## 圖的基本
### 結構
圖是由**點(Node)** 與 **邊(Edge)** 所組成
通常以點儲存資料,邊有時會包含**權重(Weight)**,例如走這條邊所需的時間等等
生活中也常有圖的應用,例如家庭成員樹狀圖,其中的邊則代表直系親屬關係,點則表示人名等資訊
### 類型
圖的類型根據邊(Edge)的性質而可先大致分成兩類:**有向圖**與**無向圖**
* **有向圖(directed graph)**:例如 $A$ 可藉由 $\overrightarrow{AB}$ 通往 $B$ ,但 $B$ ==無法==回到 $A$
* **無向圖(undirected graph)**:例如 $A$ 與 $B$ 皆可藉由 $\overline{AB}$ 互通
---
* **有權重圖(weighted graph)**:邊上含有權重
* **無權重圖(unweighted graph)**:邊上無權重 (~~整段很像廢話~~)
另外現階段討論的圖大部分都屬於「**基本圖(simple graph)**,定義如下:
1. 不存在重邊(任兩點間只會有一條邊)
2. 不存在自連邊(不會有邊從自身出發到自身結束)
因此根據以上介紹,頁頂示例圖即為 ||<font color = "f00000">無權重無向圖</font>||
### 其他名詞
#### 度數(degree)
此度數非彼度數,這裡的度數在無向圖中,指的是一個點上有==多少條邊==以自己作為端點;
在有向圖中,度數又分為 **入度(in-degree)** 和 **出度(out-degree)**
* **入度(in-degree)**:有多少邊從==別的點==連到自身
* **出度(out-degree)**:有多少邊從==自身==連到別的點
## 圖的儲存
最常用的儲存方式為使用**鄰接陣列 Adjacency Array**或**鄰接矩陣Adjacency Matrix** 儲存
將 $i$ 點可通往的點,儲存在`array[i]`中
> ps. 若一個點有很多資料要存,可使用不同的資料型態如`pair`或自訂`struct`(詳見:[struct](https://hackmd.io/@WLSH/rywX60IXWl))
例如輸入 $a, \ b$ 兩點為一無權重==無向圖==中兩點連接,部分程式則可寫為
```cpp
vector<int> adj[SIZE];
...
int a, b; cin >> a >> b;
adj[a].push_back(b);
adj[b].push_back(a);//雙向
```
以此類推無權重==有向圖==(設 $a$ 走到 $b$)寫法為:
```cpp
vector<int> adj[SIZE];
...
int a, b; cin >> a >> b;
adj[a].push_back(b);//單向
```
## 圖的遍歷- <font color = "FF3F6F">無向圖</font>
有 $N$ 個點、 $M$ 條邊,每條邊連接 $A,\ B$ 兩點
問要從 $X$ 走到 $Y$ 需要幾步?
#### 輸入
第一行有兩個正整數 $N,\ M$,分別代表點與邊的數量,並保證 $1 \le N,\ M \le 100$
接下來有 $M$ 行,每行有兩個正整數 $A、B$($1 \le A,\ B \le N$),代表兩點互通
最後一行有兩個正整數 $X,\ Y$,代表要從 $X$ 走到 $Y$
#### 輸出
如果可以到達,則輸出一個正整數回答題目所求
反之輸出一個字串 $S$ = `Can't arrive!`
#### 範例輸入1
```
4 3
1 2
3 2
3 4
1 4
```
#### 範例輸出1
```
3
```
#### 範例輸入2
```
7 8
1 2
4 3
6 7
3 6
2 4
7 5
1 3
1 5
1 7
```
#### 範例輸出2
```
2
```
#### 範例輸入3
```
3 1
1 2
1 3
```
#### 範例輸出3
```
Can't arrive!
```
### BFS
:::spoiler Sample Code
```cpp=
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int used[105] = {0}, cnt[105] = {0};
int main(){
vector<int> adj[105];
int n, m; cin >> n >> m;
for(int i = 0; i < m; i++){
int a, b; cin >> a >> b;
adj[a].push_back(b);
adj[b].push_back(a);
}
int x, y; cin >> x >> y;
used[x] = 1;
//bfs
queue<int> q;
q.push(x);
while(!q.empty()){
int npos = q.front(); q.pop();
for(auto i : adj[npos]){
if(!used[i]){
cnt[i] = cnt[npos] + 1;
used[i] = 1;
q.push(i);
}
}
}
if(cnt[y] != 0) cout << cnt[y];
else cout << "Can't arrive!";
}
```
:::
### DFS
:::spoiler Sample Code
```cpp=
#include <iostream>
#include <vector>
using namespace std;
int used[105] = {0}, ans = INT_MAX;
int x, y;
vector<int> adj[105];
void dfs(int npos, int from, int cnt){
if(npos == y) {ans = min(ans, cnt); return;}
for(auto i : adj[npos]){
if(i == from) continue;
if(!used[i]){
used[i] = 1;
dfs(i, npos, cnt + 1);
used[i] = 0;
}
}
}
int main(){
int n, m; cin >> n >> m;
for(int i = 0; i < m; i++){
int a, b; cin >> a >> b;
adj[a].push_back(b);
adj[b].push_back(a);
}
cin >> x >> y;
used[x] = 1;
//dfs
dfs(x, -1, 0);
if(ans == INT_MAX) cout << "Can't arrive!";
else cout << ans;
}
```
:::
## 圖的遍歷- <font color = "FF3F6F">有向圖</font>
有 $N$ 個點、 $M$ 條邊,每條邊連接 $A,\ B$ 兩點,==只能從 $A$ 到 $B$==
問要從 $X$ 走到 $Y$ 需要幾步?
#### 輸入
第一行有兩個正整數 $N,\ M$,分別代表點與邊的數量,並保證 $1 \le M \le N \le 100$
接下來有 $M$ 行,每行有兩個正整數 $A、B$($1 \le A,\ B \le N$),代表 $A$ 可通往 $B$
最後一行有兩個正整數 $X,\ Y$,代表要從 $X$ 走到 $Y$
#### 輸出
如果可以到達,則輸出一個正整數回答題目所求
反之輸出一個字串 $S$ = `Can't arrive!`
#### 範例輸入1
```
4 3
1 2
2 3
3 4
1 4
```
#### 範例輸出1
```
3
```
#### 範例輸入2
```
7 8
1 2
4 3
6 7
3 6
2 4
7 5
1 3
1 5
1 7
```
#### 範例輸出2
```
3
```
#### 範例輸入3
```
3 2
1 2
3 2
1 3
```
#### 範例輸出3
```
Can't arrive!
```
### BFS
:::spoiler Sample Code
```cpp=
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int used[105] = {0}, cnt[105] = {0};
int main(){
vector<int> adj[105];
int n, m; cin >> n >> m;
for(int i = 0; i < m; i++){
int a, b; cin >> a >> b;
adj[a].push_back(b);
//adj[b].push_back(a);
}
int x, y; cin >> x >> y;
used[x] = 1;
//bfs
queue<int> q;
q.push(x);
while(!q.empty()){
int npos = q.front(); q.pop();
for(auto i : adj[npos]){
if(!used[i]){
cnt[i] = cnt[npos] + 1;
used[i] = 1;
q.push(i);
}
}
}
if(cnt[y] != 0) cout << cnt[y];
else cout << "Can't arrive!";
}
```
:::
### DFS
:::spoiler Sample Code
```cpp=
#include <iostream>
#include <vector>
using namespace std;
int used[105] = {0}, ans = INT_MAX;
int x, y;
vector<int> adj[105];
void dfs(int npos, int from, int cnt){
if(npos == y) {ans = min(ans, cnt); return;}
for(auto i : adj[npos]){
if(i == from) continue;
if(!used[i]){
used[i] = 1;
dfs(i, npos, cnt + 1);
used[i] = 0;
}
}
}
int main(){
int n, m; cin >> n >> m;
for(int i = 0; i < m; i++){
int a, b; cin >> a >> b;
adj[a].push_back(b);
//adj[b].push_back(a);
}
cin >> x >> y;
used[x] = 1;
//dfs
dfs(x, -1, 0);
if(ans == INT_MAX) cout << "Can't arrive!";
else cout << ans;
}
```
:::
## 圖的遍歷- <font color = "FFFF">二維無向圖($N$ * $M$方格圖)</font>
在某些題目中常見的二維陣列地圖,也是一種無權重無向圖。因此也可以使用BFS或DFS做遍歷,前面已經提過BFS,因此這裡僅說明DFS的遍歷
### BFS
[傳送門](https://hackmd.io/TMBfQGInTUSEfLEpLIaO_Q#BFS%E6%A6%82%E5%BF%B5%E8%AA%AA%E6%98%8E)
### DFS
根據DFS(深度優先搜尋)的概念,跟BFS淹水概念不同的地方是,DFS會選擇一條路走到底,直到不能走或到達終點才回到上一個分岔點找下一條路。
>[!NOTE]DFS(無向圖) 演示圖
>||(Source:https://zh-yue.wikipedia.org/wiki/File:Depth-First-Search.gif)||
>
可以用DFS的寫法去反推,因為我們到一個條件就會`return`,因此我們會退回上一格,若還有路可以走,則會選擇另一條路繼續「D」下去,以此類推直到`return`回起點
### 實作
#### 輸入
第一行輸入一正整數 $N \leq 20$ 表示 $N * N$ 的地圖
接下來有 $N$ 行,每一行有 $N$ 個數字 $w_1,\ w_2,\ ...\ ,\ w_n \in (0, 1)$
$w_i = 0$ 代表該格不能通過
#### 輸出
輸出一個正整數 $s$ 代表從左上$(0,\ 0)$走到右下$(n-1,\ n-1)$的最短步數
### 思路
可以用DFS的[排組](https://hackmd.io/gBH64XuxQ_SAcHhSxYgaXg#Zerojudge--e446-%E6%8E%92%E5%88%97%E7%94%9F%E6%88%90)寫法去反推,因為我們到一個條件就會`return`,因此我們會退回上一格,若還有路可以走,則會選擇另一條路繼續「D」下去,以此類推直到`return`回起點。
>記得要記錄`used`來判斷是否走過,在`return`後也要還原`used`狀態
>dfs帶著`cnt`走,找最小值
:::spoiler Code
```cpp=
#include <iostream>
using namespace std;
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, 1, 0, -1};
int n, mp[50][50], used[50][50], mn = INT_MAX;
void dfs(int y, int x, int cnt){
if(y == n-1 && x == n-1){
mn = min(mn, cnt);
return;
}
for(int i = 0; i < 4; i++){
int ny = y + dy[i], nx = x + dx[i];
if(ny < 0 || ny >= n || nx < 0 || nx >= n) continue;
if(used[ny][nx]) continue;
if(mp[ny][nx] == 0) continue;
used[ny][nx] = 1;
dfs(ny, nx, cnt + 1);
used[ny][nx] = 0;
}
}
int main(){
cin >> n;
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
cin >> mp[i][j];
}
}
dfs(0, 0, 0);
cout << mn;
}
```
:::