GRAPH[1]
Graph Theory II
INDEX
Review
Shortest Paths
Spanning Tree
Review
複習
1. 術語
2. 定義
Review
Terminology
術語
vertex/node
\text{vertex} / \text{node}
頂點
頂點
edge
\text{edge}
邊
邊
arc
\text{arc}
弧(有向圖)
弧\\
(有向圖)
degree
\text{degree}
度數(相連邊數)
度數\\
(相連邊數)
1
1
2
2
1
1
in-degree
\text{in-degree}
入度
入度
0
0
2
2
0
0
out-degree
\text{out-degree}
出度
出度
1
1
0
0
1
1
path(路徑)
\text{path(路徑)}
1
1
2
2
3
3
4
4
5
5
P=(1,2,3,5)
P=(1, 2, 3, 5)
length of path
\text{length of path}
1
1
2
2
3
3
4
4
5
5
路徑長=4
路徑長=4
cycle
\text{cycle}
環
環
Review
Definitions
定義
Graph(圖)
\text{Graph(圖)}
由一組頂點和邊組成
由一組頂點和邊組成
記為 G=(V,E)V 是頂點集合,E 是邊集合
記為\ G=(V,E)\\V\ 是頂點集合,E\ 是邊集合
Directed Graph(有向圖)
\text{Directed Graph(有向圖)}
邊具有方向
邊具有方向
邊表示為 (u,v)表示從 u 指向 v
邊表示為\ (u, v)\\
表示從\ u\ 指向\ v
Directed Graph(有向圖)
\text{Directed Graph(有向圖)}
邊沒有方向
邊沒有方向
邊表示為 {u,v}表示 u 和 v 之間的關聯
邊表示為\ \{u, v\}\\
表示\ u\ 和\ v\ 之間的關聯
Simple Graph(簡單圖)
\text{Simple Graph(簡單圖)}
無多重邊和自環
無多重邊和自環
Weight Graph(加權圖)
\text{Weight Graph(加權圖)}
邊附有權重
邊附有權重
權重是一個數值,記為 w(u,v)
權重是一個數值,記為\ w(u, v)
4
4
8
8
7
7
6
6
3
3
Complete Graph(完全圖)
\text{Complete Graph(完全圖)}
每對頂點之間都有一條邊
每對頂點之間都有一條邊
完全圖的邊數為 2n(n−1)(n 是頂點數)
完全圖的邊數為\ \frac{n(n-1)}{2}(\,n\ 是頂點數)
Connected Graph(連通圖)
\text{Connected Graph(連通圖)}
無向圖中每一對頂點之間都有至少有一條路徑
無向圖中每一對頂點之間都有至少有一條路徑
Acyclic Graph(無環圖)
\text{Acyclic Graph(無環圖)}
不包含任何環的圖
不包含任何環的圖
Bipartite Graph (二分圖)
\text{Bipartite Graph (二分圖)}
頂點可以分為兩個不相交的子集 U 和 V且所有邊都連接 U 和 V 的頂點
頂點可以分為兩個不相交的子集\ U\ 和\ V\\且所有邊都連接\ U\ 和\ V\ 的頂點
Review
Unweighted Shortest Path
如何找無權圖的最短路?
如何找無權圖的最短路?
用之前講過的 BFS
用之前講過的\ \text{BFS}
Directed
\text{Directed}\\
1
1
3
3
2
2
4
4
5
5
1
1
3
3
2
2
4
4
5
5
Undirected
\text{Undirected}\\
vector<vector<int>> adj(n + 1); while (m--) { int u, v; cin >> u >> v; adj[u].push_back(v); }
vector<vector<int>> adj(n + 1); while (m--) { int a, b; cin >> a >> b; adj[a].push_back(b); adj[b].push_back(a); }
只差在存圖方式(以下都使用鄰接陣列)
只差在存圖方式(以下都使用鄰接陣列)
Review
複習一下
寫過的可以試一下
Shortest Paths
最短路
1. Bellman-Ford
2. Dijkstra
3. Floyd-Warshall
Shortest Paths
Shortest Path
Weighted
Bellman-FordO(∣V∣∣E∣)
\text{Bellman-Ford}
\\O(|V||E|)
如何找加權圖的最短路?
如何找加權圖的最短路?
有三種方法
有三種方法
DijkstraO((∣V∣+∣E∣log∣E∣)
\text{Dijkstra}
\\O((|V| + |E| \log |E|)
Floyd-WarshallO(∣V∣3)
\text{Floyd-Warshall}
\\O(|V|^3)
V 是節點數,E 是邊數
V\ 是節點數,E\ 是邊數
1
1
3
3
2
2
4
4
5
5
3
3
5
5
7
7
1
1
3
3
2
2
2
2
Shortest Paths
Bellman-Ford
找出一個點到其他點的最短距離
找出一個點到其他點的最短距離
通過尋找能縮短路徑的邊來減少距離直到無法再縮短任何距離為止
通過尋找能縮短路徑的邊來減少距離\\
直到無法再縮短任何距離為止
1
1
3
3
2
2
4
4
5
5
3
3
5
5
7
7
1
1
3
3
2
2
2
2
0
0
∞
\infty
∞
\infty
∞
\infty
∞
\infty
把起始點設為 0且到其他點的距離為 ∞
把起始點設為\ 0\\
且到其他點的距離為\ \infty
1
1
3
3
2
2
4
4
5
5
3
3
5
5
7
7
1
1
3
3
2
2
2
2
0
0
5
5
∞
\infty
3
3
7
7
從 1 開始的三條邊都可以減少距離
從\ 1\ 開始的三條邊都可以減少距離
1
1
3
3
2
2
4
4
5
5
3
3
5
5
7
7
1
1
3
3
2
2
2
2
0
0
5
5
7
7
3
3
4
4
2→5 and 3→4 會縮短距離
2\rightarrow 5\ \text{and}\ 3\rightarrow 4\ 會縮短距離
1
1
3
3
2
2
4
4
5
5
3
3
5
5
7
7
1
1
3
3
2
2
2
2
0
0
5
5
6
6
3
3
4
4
4→5
4\rightarrow 5
1
1
3
3
2
2
4
4
5
5
3
3
5
5
7
7
1
1
3
3
2
2
2
2
0
0
5
5
6
6
3
3
4
4
沒有邊可以減少距離即構成完 1 到其他點的最短距離
沒有邊可以減少距離\\
即構成完\ 1\ 到其他點的最短距離
1
1
3
3
2
2
4
4
5
5
3
3
5
5
7
7
1
1
3
3
2
2
2
2
0
0
5
5
6
6
3
3
4
4
1→5 的最短路徑
1\rightarrow 5\ 的最短路徑
Shortest Paths
Implement
for (int i = 1; i <= n; i++) distance[i] = INF; distance[x] = 0; for (int i = 1; i <= n - 1; i++) { for (auto e : edges) { int a, b, w; tie(a, b, w) = e; distance[b] = min(distance[b], distance[a] + w); } }
for (int i = 1; i <= n; i++) distance[i] = INF; distance[x] = 0; for (int i = 1; i <= n - 1; i++) { for (auto e : edges) { int a, b, w; tie(a, b, w) = e; distance[b] = min(distance[b], distance[a] + w); } }
for (int i = 1; i <= n; i++) distance[i] = INF; distance[x] = 0; for (int i = 1; i <= n - 1; i++) { for (auto e : edges) { int a, b, w; tie(a, b, w) = e; distance[b] = min(distance[b], distance[a] + w); } }
for (int i = 1; i <= n; i++) distance[i] = INF; distance[x] = 0; for (int i = 1; i <= n - 1; i++) { for (auto e : edges) { int a, b, w; tie(a, b, w) = e; distance[b] = min(distance[b], distance[a] + w); } }
for (int i = 1; i <= n; i++) distance[i] = INF; distance[x] = 0; for (int i = 1; i <= n - 1; i++) { for (auto e : edges) { int a, b, w; tie(a, b, w) = e; distance[b] = min(distance[b], distance[a] + w); } }
for (int i = 1; i <= n; i++) distance[i] = INF; distance[x] = 0; for (int i = 1; i <= n - 1; i++) { for (auto e : edges) { int a, b, w; tie(a, b, w) = e; distance[b] = min(distance[b], distance[a] + w); } }
Time Complexity: O(∣V∣∣E∣)
\text{Time Complexity: }O(|V||E|)
因為最多經過 n−1 個點就可以構成所有最短路
因為最多經過\ n - 1\ 個點就可以構成所有最短路
只需看第 n 次如果可以縮短距離代表有負環存在
只需看第\ n\ 次如果可以縮短距離代表有負環存在
2
2
3
3
4
4
1
1
−7
-7
2
2
1
1
3
3
5
5
負環
負環
Shortest Paths
Dijkstra
一點到其他點的最短距離
一點到其他點的最短距離
貪心的選擇當前距離最短的點並且更新相鄰點直到每個點都被處理過
貪心的選擇當前距離最短的點並且更新相鄰點\\
直到每個點都被處理過
3
3
2
2
4
4
1
1
5
5
2
2
6
6
5
5
9
9
2
2
1
1
∞
\infty
∞
\infty
∞
\infty
∞
\infty
0
0
把起始點設為 0且到其他點的距離為 ∞
把起始點設為\ 0\\
且到其他點的距離為\ \infty
3
3
2
2
4
4
1
1
5
5
2
2
6
6
5
5
9
9
2
2
1
1
∞
\infty
9
9
1
1
5
5
0
0
選擇一個距離最短且尚未處理過的點
選擇一個距離最短且尚未處理過的點
3
3
2
2
4
4
1
1
5
5
2
2
6
6
5
5
9
9
2
2
1
1
∞
\infty
3
3
1
1
5
5
0
0
距離為 1 的 5
距離為\ 1\ 的\ 5
3
3
2
2
4
4
1
1
5
5
2
2
6
6
5
5
9
9
2
2
1
1
9
9
3
3
1
1
5
5
0
0
距離為 3 的 4
距離為\ 3\ 的\ 4
3
3
2
2
4
4
1
1
5
5
2
2
6
6
5
5
9
9
2
2
1
1
7
7
3
3
1
1
5
5
0
0
然後會發現所有的點都只會處理一次最終構成最短距離
然後會發現所有的點都只會處理一次\\
最終構成最短距離
Shortest Paths
Proof
想當然貪心少不了證明
想當然貪心少不了證明
假設 x 是最近點distance(x)<distance(y)
假設\ x\ 是最近點\quad distance(x) < distance(y)
若且唯若distance(x)<distance(y)+w(y,...,x)則優先選擇最近點為最佳解
若且唯若\\
distance(x) < distance(y) + w(y, ..., x)\\
則優先選擇最近點為最佳解
而滿足此條件的 w(y,...,x)≥0⇒代表整張圖不能有負邊
而滿足此條件的\ w(y, ..., x) \geq 0\Rightarrow 代表整張圖不能有負邊
不然幹嘛講 Bellman-Ford
不然幹嘛講\ \text{Bellman-Ford}
最短路:1→3→4
最短路:1\rightarrow 3\rightarrow 4
Dijkstra:1→2→4
\text{Dijkstra}:1\rightarrow 2\rightarrow 4
錯了
錯了
2
2
3
3
4
4
3
3
−5
-5
1
1
2
2
6
6
負邊
負邊
Shortest Paths
Implement
for (int i = 1; i <= n; i++) dist[i] = INF; dist[start] = 0; // 起點距離為 0 pq.push({0, start}); // 預設是由大到小 while (!pq.empty()) { long long c = pq.top().front; int u = pq.top().second; pq.pop(); if (-c != dist[u]) continue; // 如果該節點已處理過 for (auto [v, w] : adj[u]) { if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; pq.push({-dist[v], v}) // 將 dist 變成負數 // 由小到大排序 } } }
for (int i = 1; i <= n; i++) dist[i] = INF; dist[start] = 0; // 起點距離為 0 pq.push({0, start}); // 預設是由大到小 while (!pq.empty()) { long long c = pq.top().front; int u = pq.top().second; pq.pop(); if (-c != dist[u]) continue; // 如果該節點已處理過 for (auto [v, w] : adj[u]) { if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; pq.push({-dist[v], v}) // 將 dist 變成負數 // 由小到大排序 } } }
for (int i = 1; i <= n; i++) dist[i] = INF; dist[start] = 0; // 起點距離為 0 pq.push({0, start}); // 預設是由大到小 while (!pq.empty()) { long long c = pq.top().front; int u = pq.top().second; pq.pop(); if (-c != dist[u]) continue; // 如果該節點已處理過 for (auto [v, w] : adj[u]) { if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; pq.push({-dist[v], v}) // 將 dist 變成負數 // 由小到大排序 } } }
for (int i = 1; i <= n; i++) dist[i] = INF; dist[start] = 0; // 起點距離為 0 pq.push({0, start}); // 預設是由大到小 while (!pq.empty()) { long long c = pq.top().front; int u = pq.top().second; pq.pop(); if (-c != dist[u]) continue; // 如果該節點已處理過 for (auto [v, w] : adj[u]) { if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; pq.push({-dist[v], v}) // 將 dist 變成負數 // 由小到大排序 } } }
for (int i = 1; i <= n; i++) dist[i] = INF; dist[start] = 0; // 起點距離為 0 pq.push({0, start}); // 預設是由大到小 while (!pq.empty()) { long long c = pq.top().front; int u = pq.top().second; pq.pop(); if (-c != dist[u]) continue; // 如果該節點已處理過 for (auto [v, w] : adj[u]) { if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; pq.push({-dist[v], v}) // 將 dist 變成負數 // 由小到大排序 } } }
for (int i = 1; i <= n; i++) dist[i] = INF; dist[start] = 0; // 起點距離為 0 pq.push({0, start}); // 預設是由大到小 while (!pq.empty()) { long long c = pq.top().front; int u = pq.top().second; pq.pop(); if (-c != dist[u]) continue; // 如果該節點已處理過 for (auto [v, w] : adj[u]) { if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; pq.push({-dist[v], v}) // 將 dist 變成負數 // 由小到大排序 } } }
for (int i = 1; i <= n; i++) dist[i] = INF; dist[start] = 0; // 起點距離為 0 pq.push({0, start}); // 預設是由大到小 while (!pq.empty()) { long long c = pq.top().front; int u = pq.top().second; pq.pop(); if (-c != dist[u]) continue; // 如果該節點已處理過 for (auto [v, w] : adj[u]) { if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; pq.push({-dist[v], v}) // 將 dist 變成負數 // 由小到大排序 } } }
Time Complexity: O(∣V∣+∣E∣log∣E∣)
\text{Time Complexity: }O(|V| + |E| \log |E|)
鄰接陣列存圖adj[a]=(b,w)
鄰接陣列存圖\quad adj[a] = (b, w)
用 priority queue 存每個點根據距離由小到大排序
用\ priority\ queue\ 存每個點\\
根據距離由小到大排序
Shortest Paths
Floyd-Warshall
所有點之間的最短距離
所有點之間的最短距離
維持一個二維陣列,嘗試所有中繼點以縮短距離
維持一個二維陣列,嘗試所有中繼點以縮短距離
3
3
2
2
4
4
1
1
5
5
2
2
7
7
5
5
9
9
2
2
1
1
12345
1\quad 2\quad 3\quad 4\quad 5
12345
1\\
2\\
3\\
4\\
5
所有節點到自己的距離設為為零,且 (a,b) 之間有邊的則設為其權重
所有節點到自己的距離設為為零,且\ (a,b)\ 之間有邊的則設為其權重
05 ∞91
0\quad 5\ \ \infty \quad 9\quad 1
502 ∞ ∞
5\quad 0\quad 2\ \ \infty\ \ \infty
207 ∞
2\quad 0\quad 7\ \ \infty
9 ∞702
9\ \ \infty\quad 7\quad 0\quad 2
1 ∞ ∞20
1\ \ \infty\ \ \infty\quad 2\quad 0
∞
\infty
12345
1\quad 2\quad 3\quad 4\quad 5
12345
1\\
2\\
3\\
4\\
5
first, 用 1 作為中繼點
first,\ 用\ 1\ 作為中繼點
05 ∞91
0\quad 5\ \ \infty \quad 9\quad 1
502
5\quad 0\quad 2
∞ 207 ∞
\infty\ \ 2\quad 0\quad 7\ \ \infty
702
7\quad 0\quad 2
∞20
\infty\quad 2\quad 0
146
14\quad 6
9
9
14
14
6
6
9
9
12345
1\quad 2\quad 3\quad 4\quad 5
12345
1\\
2\\
3\\
4\\
5
用 2 作為中繼點
用\ 2\ 作為中繼點
91
9\quad 1
502
5\quad 0\quad 2
207
2\quad 0\quad 7
702
7\quad 0\quad 2
20
2\quad 0
146
14\quad 6
9
9
14
14
6
6
9
9
05
0\quad 5
7
7
7
7
8
8
8
8
12345
1\quad 2\quad 3\quad 4\quad 5
12345
1\\
2\\
3\\
4\\
5
用 3 作為中繼點
用\ 3\ 作為中繼點
91
9\quad 1
502
5\quad 0\quad 2
207
2\quad 0\quad 7
702
7\quad 0\quad 2
20
2\quad 0
6
6
9
9
9
9
6
6
9
9
05
0\quad 5
7
7
7
7
8
8
8
8
9
9
12345
1\quad 2\quad 3\quad 4\quad 5
12345
1\\
2\\
3\\
4\\
5
直到完成整張表
直到完成整張表
05731
0\quad 5\quad 7\quad 3\quad 1
50286
5\quad 0\quad 2\quad 8\quad 6
72078
7\quad 2\quad 0\quad 7\quad 8
38702
3\quad 8\quad 7\quad 0\quad 2
16820
1\quad 6\quad 8\quad 2\quad 0
12345
1\quad 2\quad 3\quad 4\quad 5
12345
1\\
2\\
3\\
4\\
5
2→4 的最短路是 8
2\rightarrow 4\ 的最短路是\ 8
05731
0\quad 5\quad 7\quad 3\quad 1
502
5\quad 0\quad 2
72078
7\quad 2\quad 0\quad 7\quad 8
38702
3\quad 8\quad 7\quad 0\quad 2
16820
1\quad 6\quad 8\quad 2\quad 0
8
8
6
6
Shortest Paths
Implement
Time Complexity: O(n3)
\text{Time Complexity: }O(n^3)
使用鄰接矩陣存圖adj[a][b]=w
使用鄰接矩陣存圖\quad adj[a][b] = w
for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) distance[i][j] = 0; else if (adj[i][j]) distance[i][j] = adj[i][j]; else distance[i][j] = INF; } }
for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) distance[i][j] = 0; else if (adj[i][j]) distance[i][j] = adj[i][j]; else distance[i][j] = INF; } }
for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) distance[i][j] = 0; else if (adj[i][j]) distance[i][j] = adj[i][j]; else distance[i][j] = INF; } }
for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) distance[i][j] = 0; else if (adj[i][j]) distance[i][j] = adj[i][j]; else distance[i][j] = INF; } }
for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) distance[i][j] = 0; else if (adj[i][j]) distance[i][j] = adj[i][j]; else distance[i][j] = INF; } }
尋找最短路:
尋找最短路:
for (int k = 1; k <= n; k++) { // 中繼點,記得放在最外圈 for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { distance[i][j] = min(distance[i][j], distance[i][k]+distance[k][j]); } } }
for (int k = 1; k <= n; k++) { // 中繼點,記得放在最外圈 for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { distance[i][j] = min(distance[i][j], distance[i][k]+distance[k][j]); } } }
for (int k = 1; k <= n; k++) { // 中繼點,記得放在最外圈 for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { distance[i][j] = min(distance[i][j], distance[i][k]+distance[k][j]); } } }
for (int k = 1; k <= n; k++) { // 中繼點,記得放在最外圈 for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { distance[i][j] = min(distance[i][j], distance[i][k]+distance[k][j]); } } }
忘記 k 迴圈放哪沒關係,你只需要整個跑三遍他就一定會是對的
忘記\ k\ 迴圈放哪沒關係,你只需要整個跑三遍他就一定會是對的
Shortest Paths
負環
在圖論正常來說有負環存在的圖找最短路徑沒意義(你可以一直繞直到無窮小)
在圖論正常來說有負環存在的圖找最短路徑沒意義\\
(你可以一直繞直到無窮小)
Bellman-Ford:判斷第 n 次迴圈是否縮減距離
\text{Bellman-Ford}:判斷第\ n\ 次迴圈是否縮減距離
Dijkstra:不能處理負邊
\text{Dijkstra}:不能處理負邊
Floyd-Warshall:如果 distance[i][i]<0
\text{Floyd-Warshall}:如果\ distance[i][i]<0
而有時候你只是想要判有沒有負環
而有時候你只是想要判有沒有負環
但其實上面只適用於有向圖
但其實上面只適用於\bf{有向圖}
我們應該很難說服自己這是個負環,但他是。
我們應該很難說服自己這是個負環,但他是。
1
1
4
4
−7
-7
不然......
不然......
Shortest Paths
Summary
小結
小結
單源
單源
單源
單源
多源
多源
任意圖
任意圖
無負邊
無負邊
任意圖
任意圖
O(∣V∣∣E∣)
O(|V||E|)
O(∣V∣+∣E∣log∣E∣)
O(|V| + |E| \log |E|)
O(∣V∣3)
O(|V|^3)
檢查負環
檢查負環
快速單點源
快速單點源
全點對
全點對
Bellman-Ford
\text{Bellman-Ford}
Dijkstra
\text{Dijkstra}
Floyd-Warshall
\text{Floyd-Warshall}
題單
Shortest Paths
Spanning Tree
生成樹
1. DSU
2. Kruskal's
3. Prim's
Spanning Tree
Minimum Spanning Tree
最小生成樹
生成樹是一棵包含圖裡面所有點和一條通過所有點的路徑的樹
生成樹是一棵\\包含圖裡面所有點和一條通過所有點的路徑的\bf{樹}
(無向聯通無環圖)
(無向聯通無環圖)
他的權重就是所有邊的權重總和
他的權重就是所有邊的權重總和
3+5+2+3+7=20
3 + 5 + 2 + 3 + 7 = 20
而最小生成樹就是權重最小的生成樹
而最小生成樹就是權重最小的生成樹
2
2
5
5
3
3
6
6
4
4
6
6
5
5
2
2
3
3
9
9
7
7
1
1
3
3
5
5
這是張圖
這是張圖
2
2
5
5
3
3
6
6
4
4
2
2
3
3
7
7
1
1
3
3
5
5
⇒
\Rightarrow
這是他的生成樹
這是他的生成樹
Spanning Tree
題目
找最小生成樹要幹嘛?
找最小生成樹要幹嘛?
⇒其實就是找最小生成樹的權重
\Rightarrow 其實就是找最小生成樹的權重
有兩種方法可以構成最小生成樹
有兩種方法可以構成最小生成樹
Kruskal’s&Prim’s
\text{Kruskal's}\quad \& \quad \text{Prim's}
n 個城市 m 條道路,每一條路都是破損的修補道路使每兩個城市間皆有一條路的最小代價為多少?
n\ 個城市\ m\ 條道路,每一條路都是破損的\\
修補道路使每兩個城市間皆有一條路的最小代價為多少?
Spanning Tree
Kruskal's algorithm
將邊由小到大遍歷,如果該邊不構成環把它加到生成樹裡,最終構成一個樹。
將邊由小到大遍歷,如果該邊不構成環\\
把它加到生成樹裡,最終構成一個樹。
2
2
5
5
3
3
6
6
4
4
1
1
初始所有點不相連
初始所有點不相連
2
2
5
5
3
3
6
6
4
4
1
1
第一個邊把 {5} 和 {6} 相連
第一個邊把\ \{5\}\ 和\ \{6\}\ 相連
2
2
2
2
5
5
3
3
6
6
4
4
1
1
1−2, 3−6 和 1−5
1\!-\!2,\ 3\!-\!6\ 和\ 1\!-\!5
2
2
3
3
3
3
5
5
2
2
5
5
3
3
6
6
4
4
1
1
下兩個邊 2−3, 2−5 會構成環,不加入最後 4−6 ,構成最小生成樹
下兩個邊\ 2\!-\!3,\ 2\!-\!5\ 會構成環,不加入\\
最後\ 4\!-\!6\ ,構成最小生成樹
2
2
3
3
3
3
5
5
7
7
5
5
2
2
3
3
9
9
3
3
1
1
6
6
3
3
4
4
5
5
5
5
6
6
7
7
2
2
先把所有邊按照權重排序
先把所有邊按照權重排序
edgeweight
edge\quad weight
2
2
5−6
5\!-\!6
1−2
1\!-\!2
3
3
3−6
3\!-\!6
3
3
1−5
1\!-\!5
5
5
2−3
2\!-\!3
5
5
2−5
2\!-\!5
6
6
4−6
4\!-\!6
7
7
3−4
3\!-\!4
9
9
Spanning Tree
Proof
這很貪心對吧
這很貪心對吧
假設剛剛的圖沒有包含 5−6,他可能會長:
假設剛剛的圖沒有包含\ 5\!-\!6,他可能會長:
但他一定不會是最小生成樹
但他一定不會是最小生成樹
因為如果我們移除一個邊並且加上 5−6,會產生一個更小的生成樹
因為如果我們移除一個邊並且加上\ 5\!-\!6,會產生一個更小的生成樹
⇒所以在不破壞樹性質的情況下加入最小邊則可構成最小生成樹
\Rightarrow 所以在不破壞樹性質的情況下加入最小邊則可構成最小生成樹
2
2
5
5
3
3
6
6
4
4
1
1
⇒
\Rightarrow
2
2
5
5
3
3
6
6
4
4
1
1
2
2
Spanning Tree
Implement
併查集(Disjoint Set Union)
要怎麼判兩點是否會構成環?
要怎麼判兩點是否會構成環?
所以我們需要一個可以快速查詢和合併的結構
所以我們需要一個可以快速查詢和合併的結構\\
看他們是不是在同一個集合
看他們是不是在同一個集合
⇒對於每一個集合,都作一個鏈指向代表
\Rightarrow 對於每一個集合,都作一個鏈指向代表\\
就能根據代表判斷他們是否在同個集合
就能根據代表判斷他們是否在同個集合
4
4
1
1
7
7
5
5
3
3
6
6
8
8
2
2
以上三個集合的代表為 4, 5 和 2
以上三個集合的代表為\ 4,\ 5\ 和\ 2
4
4
1
1
7
7
3
3
6
6
8
8
2
2
也可以讓他們合併,代表變為 2
也可以讓他們合併,代表變為\ 2
Shortest Paths
Implement
for (int i = 1; i <= n; i++) link[i] = i; // 各自為代表 for (int i = 1; i <= n; i++) size[i] = 1; // 紀錄代表 i 所在集合的大小
查找
查找
使用 link 陣列紀錄對於每個點,在鏈上的下個節點(如果自身是代表則指向自己)
使用\ link\ 陣列紀錄對於每個點,在鏈上的下個節點\\
(如果自身是代表則指向自己)
int find(int x) { while (x != link[x]) x = link[x]; return x; }
合併
合併
void unite(int a, int b) { a = find(a); b = find(b); // 這是啟發式合併,可以避免惡意測資 if (size[a] < size[b]) swap(a, b); size[a] += size[b]; link[b] = a; }
Spanning Tree
Prim's algorithm
雖然大多數人都用 Kruskal’s,但還是來介紹另外一個算法
雖然大多數人都用\ \text{Kruskal's},但還是來介紹另外一個算法
從任意一個點開始,每次都找可加入的點中代價最少的加入(跟 Dijkstra 很像,但是找最小權重邊)
從任意一個點開始,每次都找可加入的點中代價最少的加入\\
(跟\ \text{Dijkstra}\ 很像,但是找最小權重邊)
2
2
5
5
3
3
6
6
4
4
6
6
5
5
2
2
3
3
9
9
7
7
1
1
3
3
5
5
使用跟剛剛一樣的圖
使用跟剛剛一樣的圖
2
2
5
5
3
3
6
6
4
4
1
1
初始沒有任何邊
初始沒有任何邊
2
2
5
5
3
3
6
6
4
4
1
1
隨機選擇一個邊(1)加入節點 2
隨機選擇一個邊(1)\\
加入節點\ 2
3
3
2
2
5
5
3
3
6
6
4
4
1
1
接下來有兩個權重為 5 的邊可以選擇加入 3 或 5
接下來有兩個權重為\ 5\ 的邊\\
可以選擇加入\ 3\ 或\ 5
3
3
5
5
2
2
5
5
3
3
6
6
4
4
1
1
重複直到所有節點都被加入
重複直到所有節點都被加入
3
3
5
5
2
2
3
3
7
7
實作也跟 Dijkstra 一樣可以使用 priority queue
實作也跟\ \text{Dijkstra}\ 一樣可以使用\ priority\ queue
Shortest Paths
AC Code
#include <iostream> #include <vector> #include <algorithm> #include <utility> using namespace std; // 這邊的 DSU 是用路徑壓縮,有興趣可以去查一下 struct DSU { vector<int> boss; DSU(int n) : boss(n + 1) { for (int i = 1; i <= n; i++) { boss[i] = i; } } int query(int a) { if (boss[a] == a) return a; return boss[a] = query(boss[a]); } bool unite(int a, int b) { a = query(a), b = query(b); if (a == b) return false; boss[a] = query(b); return true; } }; int main() { int n, m; cin >> n >> m; vector<pair<int, pair<int, int>>> adj; for (int i = 0; i < m; i++) { int a, b, c; cin >> a >> b >> c; adj.push_back({c, {a, b}}); } sort(adj.begin(), adj.end()); DSU dsu(n); long long ans = 0; int node = 0; for (int i = 0; i < m; i++) { int a, b, c; a = adj[i].second.first; b = adj[i].second.second; c = adj[i].first; if (dsu.unite(a, b)) { ans += c; node++; } } if (node != n - 1) cout << "IMPOSSIBLE\n"; else cout << ans << '\n'; }
Time Complexity≈O(mlogn)
\text{Time Complexity}\approx O(m \log n)
實際上是 O(mlog2+f/nn)
實際上是\ O(m \log_{2+f/n} n)
掰掰
GRAPH[1] Graph Theory II keaucucal
圖論 II
By keaucucal
圖論 II
- 247