圖論1
存圖資料結構、DFS/BFS、MST
什麼是圖論?
圖論aka
對圖的拓樸化研究
就是一堆點跟線啦
柯尼斯堡七橋問題
對圖的拓樸研究
aka
將圖簡化為點跟線
什麼是圖
圖的定義
點加上線
或嚴格來說
點的集合加上邊的集合
\(G = (V, E)\)
圖的術語
點
圖上一個個點
邊
圖上一個個邊
接下來稍微難個一點點
邊的方向性
對於有些邊
我們可能會限定只能從其中一端走向另一端
我們將這種邊稱為-有向邊
點度(degree)
代表這個點連結了幾條邊
對於有向圖會再進一步分類
- 入度:代表通往這個點的邊數
- 出度:代表離開這個點的邊數
路徑 (path)
- 一組邊
- 每條邊的終點是下條邊的起點
- 起終點相同的路徑稱為環(loop)
- 有時會強調簡單路徑,代表沒有經過重複的點/邊
- 簡單環同理
點間的關係
- 相鄰:兩點直接相接
- 連通:兩點間存在路徑
A
C
B
\((A,B),(B,C)\)相鄰且連通
\((A,C)\)間則只有聯通
特別的圖
連通圖
對於圖中的任一點
都與任意其他一點間存在路徑
樹
邊的數量為 \(V-1\) 的連通圖會是一棵樹
有時我們會將任意一個點定為根
即稱為有根樹
像接下來這樣
現實生活中的樹
資訊領域中的樹
實際上,他長這樣
A
B
C
F
G
H
E
D
根節點
葉節點
父節點
子節點
完全圖
圖中任意兩點都存在邊
二分圖
可以被切為兩個不相連的點集
有向無環圖
顧名思義,沒有環的有向圖
這類圖可以進行拓樸排序(Topological Sort)
1
5
4
2
3
圖的資料結構
相鄰矩陣
用一個二維矩陣存取每一個點間是否相鄰及邊權
相鄰串列
用\(V\)個串列去存取每個點與哪些邊相鄰
相鄰矩陣
開一個\(V * V\)的二維陣列
陣列的\(i,j\)項表示
\(i\)點與\(j\)點是否相鄰
或者也可以存取邊權
範例
X | 1 | 2 | 3 | 4 | 5 | 6 |
1 | 0 | 0 | 0 | 1 | 1 | 0 |
2 | 0 | 0 | 0 | 1 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 1 | 1 |
4 | 1 | 1 | 0 | 0 | 1 | 1 |
5 | 1 | 0 | 1 | 1 | 0 | 0 |
6 | 0 | 0 | 1 | 1 | 0 | 0 |
1
2
3
4
5
6
無向圖中會左下右上對稱!
相鄰串列
開\(V\)個串列
對於第\(i\)個串列,存\(i\)點相鄰的邊
範例
1 | 4,5 |
2 | 4 |
3 | 5,6 |
4 | 1,2,5,6 |
5 | 3,4 |
6 | 3,4 |
1
2
3
4
5
6
空間複雜度比較
相鄰矩陣是\(O(V^2)\)
相鄰串列是\(O(E)\)
相鄰串列的空間複雜度比較好
時間
以時間來說,可以分為遍歷圖和判斷兩點是否相鄰
以遍歷圖來說
矩陣是\(O(V^2)\)
串列是\(O(E)\)
那判斷呢?
矩陣:\(O(1)\) , 串列:\(O(V)\)
圖的遍歷
深度優先搜尋
也就是每次朝最深的那個做
每次遍歷到一個點就繼續遍歷相鄰的所有點
*但要注意不能走到已經遍歷過的點!
DFS範例
1
2
3
4
5
6
從點1開始遞迴
1-4-2-5-3-6
Example Code
//還記得前面建圖的函數ㄇ,繼續下去喔
bool visited[maxn];
void dfs(int v){
visited[v] = true; //表示點v已被造訪
/*
做你在遍歷時想做的事
*/
for (auto i : graph[v]){
if (visited[i]) //略過已造訪過的點
continue;
dfs(i);
}
}
廣度優先搜尋
相對於深度優先
廣度優先會優先將深度相同的點遍歷完
再去遍歷下一組深度的點
*一樣要注意不要遍歷到已被遍歷的點
透過queue維護
BFS範例
1
2
3
4
5
6
by深度
1
4 5
2 6 3
注意
維護時要注意不要將已經被遍歷過的點加入queue中
否則空間複雜度會變成\(O(V+E)\)而不是\(O(V)\)
而一被加入queue中即可視為已被遍歷了
Code Example
//一樣接著前面喔
#include <bitset>
#include <queue>
void bfs(int startVercile){
queue <int> toBFS;
bitset <MAXN> visited;
toBFS.push(startVercile);
visited[toBFS.front()] = true;
while (toBFS.size()){ //當queue內尚有元素時執行
/*
做你在遍歷時想做的事
*/
for (auto i : graph[toBFS.front()]){
if (!visited[i.first]) //如果該點尚未被造訪則放入queue中
toBFS.push(i.first);
}
}
}
MST
What is MST
Minimum/Maximum Spanning Tree
最小/大棵的生成樹
那要怎麼做出這棵樹呢
生成樹?
生成樹指的是
對於一張圖,我們只其中取一些邊使所有節點都相連
而且這些部份邊必須形成一棵樹
先討論性質!
如果一棵生成樹的邊權和最小,那它有什麼性質?
Hint:先用直覺想一下,不要管證明
邊權越小的邊
我們越希望它被加入最小生成樹
Ans點我
我們獲得了貪婪原則!
Prim's Algorithm
Prim's Algorithm
- 先隨意挑選一個起點
- 將加入的點連向MST外的邊加入考慮範圍
- 從考慮範圍中取連外邊中邊權最小的邊加入MST
- 重複2~3,直到有\(V-1\)條邊為止
還記得剛剛那張圖吧
我們加上邊權
1
2
3
4
5
6
7
2
1
3
3
2
2
試試看
從點1開始
1
2
3
4
5
6
7
2
1
3
3
2
2
取邊\((1,4),w = 2\)
取邊\((4,5),w = 1\)
取邊\((5,3),w = 2\)
取邊\((3,6),w = 2\)
取邊\((2,4),w = 7\)
複雜度多少?
由於要維護一個持續取最小值的集合
因此用priority queue去維護
而需要對priority queue操作\(V+E\)次
每次操作耗時\(log(E)\)
因此總複雜度為\(O((V+E)log(E))\)
Kruskal's Algorithm
另一種貪婪法
如果現在給你兩棵MST
要將這兩棵樹連起來
你會選擇哪條邊?
邊權最小的那條邊R
Ans點我
Kruskal's Algorithm
- 依邊權排序邊
- 每次取邊權最小的邊
- 若連結兩棵MST,就將MST合併
- 重複2~3,直到有\(V-1\)條邊
合併?
DSU
並查集Disjoint-Set Union
- 近乎\(O(1)\)查詢
- 近乎\(O(1)\)合併
How?
首先,將所有元素視為獨立集合
且每個集合的最小值為"代表項"
並且每個元素都有一個父元素
代表項的父元素定為本身
只要對代表項做事就能合併與判斷
DSU怎麼做
合併3,9
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
1
2
3
4
5
6
7
8
9
10
3
合併4,5
4
合併3,5
3
先查代表項再合併!
要查詢一個點的代表項,只要一直往父元素查詢就好了
複雜度好像沒有很好?
如果我們每次合併\((1,2),(2,3),(3,4)...(n-1,n)\)
此時查詢第\(n\)個點的複雜度會是\(O(n)\)
於是我們需要一個酷酷的東西
路徑壓縮!!
路徑壓縮
在查詢時,一路遞迴上去時
把經過每一項的父元素都直接設為代表項
這樣之後查詢就會是\(O(1)\)
(實際上均攤複雜度會落在\(O(\alpha(n))\)
但\(\alpha\)函數在正常數字範圍內都小於4
因此基本上可以視為\(O(1)\))
範例程式碼
//DSUUUUUUUUUUU
int dsu[MAXN]; //第i項表示i點的父節點
void Init(){
for (int i=0;i<MAXN;++i)
dsu[i] = i;
}
//先將每項的父元素設為本身
int query(int k){
if (k == dsu[k])
return k;
dsu[k] = query(dsu[k]);
return dsu[k];
}
void modify (int x,int y){
int xAncestor = query(x);
int yAncestor = query(y);
if (xAncestor != yAncestor){
dsu[xAncestor] = yAncestor;
}
}
回到MST
再用kruskal找找看
先對邊以邊權sort
1
2
3
4
5
6
7
2
1
3
3
2
2
檢查\((4,5),w = 1\),放入
檢查\((1,4),w = 2\),放入
檢查\((3,5),w = 2\),放入
檢查\((3,6),w = 2\),放入
檢查\((1,5),w = 3\),不放入
檢查\((4,6),w = 3\),不放入
檢查\((2,4),w = 7\),放入
得到MST了!
複雜度評估
排序的複雜度會落在\(O(ElogE)\)
而接下來會做\(E\)次判斷
因此複雜度取較大的\(O(ElogE)\)
例題們
健康快樂又好玩ㄉ例題們
給你一張圖,求最小生成樹權重和,圖不連通則輸出-1
給你一張連通圖,邊權均為0或1
問你是否存在邊權和為\(k\)的生成樹
\(V \leq 10^5, E \leq 10^5,k \leq V-1\)
在三維坐標系上有\(n\)個點,在兩點間建立路徑的花費為距離的平方,求使所有點連通的最小花費
\(n < 5000\)
圖論1
By yungyao
圖論1
- 501