The structure of graph
[Note] Adjacency list
有一圖 G = (V, E), |V| = n, |E| = e,需要 n 條相鄰串列(vertex[1...n] : pointer)來表達,其中 vertex[i] 代表頂點 i 的相鄰串列,紀錄所有與頂點 i 相鄰的頂點編號
- 以「相鄰串列」表示無向圖時,因為 邊(i,j) = 邊(j,i)(一條邊有兩個頂點),所以串列需要 2×e 個節點來儲存這張圖
- 當圖的邊少時,可以取得優勢
Example(無向圖)
求頂點 i 的「Degree」需要的時間複雜度為?
即為 vertex[i] 的串列長度,所以需要的時間複雜度為 O(\(V_i\)串列長度) ≦ O(e)
Example(無向圖)
求圖上有多少邊的時間複雜度為?
\(e = \frac 12\sum_{i = 1}^n (V_i \;Degree)\\ \therefore e = \frac 12 \sum_{i = 1}^n (V_i \;串列長度)\\ = O(n+e)\)
無向圖上最多可以有 \(\binom {n}{2} = \frac{n(n-1)}{2} = \frac{n^2-n}{2}\) 條邊,從上述又可以得知如果要追蹤以「相鄰串列」表示的無向圖上之邊,最多的時間複雜度為 \(O(n + \frac{n^2-n}{2}) = O(n^2)\)
Example
若以「相鄰串列」表示有向圖,要求頂點的「Out-degree」與「In-degree」,分別需要的時間複雜度為?
「Out-degree」:vertex[i] 的串列長度,所以也為 O(e)
「In-degree」:因為必需追蹤所有點以找到指向 i 的頂點為何,所以全部的串列都要確認,時間複雜度為 \(O(n^2)\)
相鄰串列與相鄰陣列比較表
Adjacency matrix | Adjacency list | |
---|---|---|
空間需求 | \(O(n^2)\) | O(n+e) |
表示頂點數多邊數少之圖 | 需要\(O(n^2)\)且為稀疏矩陣,不適合 | 適合 |
表示邊數多(e=O\((n^2)\))之圖 | 適合 | 需要\(O(n+O(n^2))\),不適合 |
判斷兩頂點間是否存在邊 | 時間複雜度 O(1),適合 | 時間複雜度 O(e),不適合 |
求圖上的邊數(判斷連通...) | 時間複雜度 \(O(n^2)\),不適合 | 時間複雜度 O(n+e),適合 |
Adjacency multilist(相鄰多元串列)
欲表示圖 G = (V,E), |V| = n, |E| = e
- 每一條邊以一個節點表示
- 指標陣列(vertex[1...n] : pointer)
- Vertex[i] 指向第一個包含 \(V_i\) 的邊節點
- 空間複雜度:O(n+e)
Index array
欲表示圖 G = (V,E), |V| = n, |E| = e
一個一維陣列(Array[1... 2×e])紀錄所有點之相鄰頂點編號
一個一維陣列為「Index」(Index[1...n])表示頂點 i 在 Array 中紀錄的起始位置
- 空間複雜度:O(n+e)
Incident matrix (離散數學)
欲表示圖 G = (V,E), |V| = n, |E| = e
- 一個二維陣列(A[1...n, 1...e] : boolean)
- 若圖中存在邊 \(e_k\)=(i,j),則 A[i, \(e_k\)]=A[j, \(e_k\)]=1,其餘為 0
圖的追蹤
G = (V,E), |V| = n, |E| = e
- 比較表
- 在演算法書籍中,使用「Adjacency list」作深度追蹤與廣度追蹤時,時間複雜度分析考慮了 visit[1...n] 陣列初值的設定,所以為 O(n+e),而資料結構書籍則無考慮
DFS | BFS | |
---|---|---|
佐結構 | Stack | Queue |
Adjacency Matrix(時間複雜度) | \(O(n^2)\) | \(O(n^2)\) |
Adjacency List(時間複雜度)[DS版] | O(e) | O(e) |
Adjacency List(時間複雜度)[Algorithm版] | O(n+e) | O(n+e) |
DS 書籍版本
Depth first search
以無項圖為主,此演算法使用「相鄰矩陣」
深度優先追蹤法以「Stack」實作,所以使用遞迴法實現
- Algorithm
1 | // Global variable: |
- Depth first search 的路線並不唯一
- 通常頂點編號小者優先走訪,考試時路線才會唯一
Breath first search
廣度優先追蹤以「Queue」實作
- Algorithm
1 | // Global variable: |
無向圖二元樹
- (見上圖) 在圖中執行 DFS(1) 類似在二元樹中作「前序追蹤」
- (見上圖) 在圖中執行 BFS(1) 類似在二元樹中作「層序追蹤」
Example (實現「Level-order」追蹤演算法)
1 | // v: 根節點 |
Algorithm 書籍版本
Depth first search
- Algorithm
- u.Color
- White:尚未拜訪
- Gray:已拜訪,但尚未拜訪完相鄰的頂點
- Black:拜訪完相鄰的頂點
- u.d
- 從起點拜訪 u 點的時間
- u.f
- u 點拜訪完所有相鄰頂點的時間
- u.parent
- 在 DFS 拜訪樹中,為 u 的父節點
- u.Color
1 | // time: Global variable 用來記錄追蹤的路徑 |
有向圖
使用 DFS 追蹤時,會將圖分為四種邊(詳見演算法文章中的圖論)
- Tree edge:DFS 拜訪經過的邊
- Back edge:指向祖先的邊
- Forward edge:指向後代的邊
- Cross edge:跨不同子樹的邊
在無向圖上,使用 DFS 追蹤只會將圖分成
- Tree edge
- Back edge
Breath first search
- Algorithm
- u.Color
- White:尚未拜訪
- Gray:已拜訪,但尚未拜訪完相鄰的頂點
- Black:拜訪完相鄰的頂點
- u.d
- 從起點到 u 點路徑的邊數(深度)
- u.parent
- 在 BFS 拜訪樹中,為 u 的父節點
- u.Color
1 | // G: adjacency list |
- Time complexity
- Line A:作「Dequeue」需要 O(1),共有 |V| 個頂點
- O(1)×|V| = O(|V|)
- Loop A :找到每個頂點的相鄰頂點
- O(2|E|)
- 總時間複雜度
- O(|V|+|E|)
- Line A:作「Dequeue」需要 O(1),共有 |V| 個頂點
無向圖若邊上無權重,求某起點到各頂點之「最短路徑」可以使用廣度優先追蹤之生成樹即可
追蹤的應用
Example
無向圖 G,判斷是否有連通?
Algorithm
使用 DFS([DS 版]) 或 BFS 追蹤 G
若 visit 陣列中有頂點尚未被拜訪即為不連通
Time complexity
- 相鄰矩陣:\(O(|V|^2) + O(|V|) = O(|V|^2)\)
- 相鄰串列:O(|V|+|E|) + O(V) = O(|V|+|E|)
Example
有向圖 G,判斷是否有環路?
- Algorithm
- 用 DFS,若存在「Back edge」即存在環路
- Time complexity
- 相鄰矩陣:\(O(|V|^2)\)
- 相鄰串列:O(|V|+|E|) (當圖上存在「Hamilton cycle」,為最好的情況,因為只要搜尋過所有點即可找到)
Example
無向圖 G,判斷是否有環路?(找「Back edge」即可)
如何判斷「Back edge」?
- 修改程式(因為無項圖的邊可來回,為了防止同一邊追蹤兩次須加上
w.parent != u
)
1
2
3
4 ....
for each w in G.adj[u] {
if(w.color==gray && w.parent!=u) // 為「Back edge」
....
Spanning tree
針對無向圖
- 若 G 為非連通圖,則無「Spanning tree」
- 若 G 為連通圖,則「Spanning tree」≧ 1
Example (True or False)
一個連通無向圖 G,則 G 上任意的「Spanning tree」,必定包含 ≧ 1 條「共同的邊」?
False(見下圖)
Minimum spanning tree
若圖中有多條邊具有相同的值,則「Minimum spanning tree」≧ 1
若每條邊權重皆不同則「Minimum spanning tree」唯一
應用
- 電路布局成本最小化
- 連結 n 城市之最少建設成本
Kruskal's algorithm [Algorithm 書上版本]
G = (V,E), |V| = n, |E| = e
- 最多作 |E| 回合,而每一回合主要有兩個工作
- 挑選最小成本的邊 (u, v)
- 邊的權重集合若使用「Heap」維護,則時間複雜度則為「Delete min」的成本(O(log |E|))
- 判斷邊 (u, v) 加入到 s 之中是否形成環路(O(1))
- 使用一個「Disjoint set」維護「Minimum spanning tree」上的點,在加入新的邊時,使用邊上兩頂點在「Disjoint set」中尋找(
Find(u)==Find(v)?
),如果兩點都存在於同一個集合中,則形成環路,不加入該邊,反之將該邊加入(Union(u,v)
)「Minimum spanning tree」中 - 若「Disjoint set」採用「Union by height」與「Find with path compression」,則
Union(u,v)
Find(u)
Find(v)
需要的時間複雜度為 O(1)
- 使用一個「Disjoint set」維護「Minimum spanning tree」上的點,在加入新的邊時,使用邊上兩頂點在「Disjoint set」中尋找(
- 挑選最小成本的邊 (u, v)
- Time complexity
- O( |E|×log|E|)
- Algorithm
1 | Mst_kruskal(G, W) { |
- Time complexity
- O(|V|) + O(|E|log|E|)+O(|E|) = O(|E|log|E|)
- 因為 |E| ≦ |V|×|V|,所以 O(|E|log|E|) = O(|E|log|\(V^2\)|) = O(|E|log|V|)
Example(102 交通大學資料結構與演算法)
- The running time of Kruskal's algorithm for a connected undirected weighted graph G = (V, E) is ___.
O(∣E∣log∣V∣)
- Suppose that all edge weights in a graph G are integers in the range from 1 to ∣V∣. How fast can you make Kruskal's algorithm run?
O(∣E∣ α(∣V∣)),α 為「Ackermann function」反函數
Prim's algorithm [DS 書上版本]
- Time complexity
- O(||)
- Algorithm
1 | // G: graph |
Time complexity (用「Binary heap」作為「Priority queue」)
初值建立 O(|V|)
建立「Priority queue」 O(|V|)
Line A:
Extract_min(q)
作一次需要 O(log|V|)- 共作 |V| 次,需要 O(|V|log|V|)
Loop A:總共會檢視 2 ×|E| 個頂點
Line B:
v.key = W[u,v]
相當於是作「Decrease key of node in heap」因為為「Binary heap」,所以需要 O(log|V|) 的時間複雜度
迴圈總共的時間複雜度為 O(|E|log|V|)
總共需要 O(|V|) + O(|V|) + O(|V|log|V|) + O(|E|log|V|) ,因為 |E|≧ |V|-1 ,所以時間複雜度為 O(|E|log|V|)
- 與「Kruskal」的複雜度一致
- 可以使用「Fibonacci heap」再進行加速
Time complexity (用「Fibonacci heap」作為「Priority queue」)
初值建立 O(|V|)
建立「Priority queue」 O(|V|)
Line A:
Extract_min(q)
作一次需要 O(log|V|)- 共作 |V| 次,需要 **O(|V|log|V|)
Loop A:總共會檢視 2 ×|E| 個頂點
Line B:
v.key = W[u,v]
相當於是作「Decrease key of node in heap」因為為「Fibonacci heap」,所以需要 O(1) 的時間複雜度
迴圈總共的時間複雜度為 O(|E|)
總共需要 O(|V|) + O(|V|) + O(|V|log|V|) + O(|E|) ,因為 |E|≧ |V|-1 ,所以時間複雜度為 O(|V|log|V|+|E|)
Sollin's algorithm
步驟:(開始時先將每個頂點視為獨立的數根)
- 針對每棵樹,各自挑出「Minimum cost tree edge」
- 刪除重複挑出的邊
- 重複第一步與第二步直到成為「最小生成樹」
- 在循環迭代中
- 每棵樹都會合併成一棵較大的子樹
- 每次會使子樹的數量至少減少一半
- 所以循環迭代的總次數為O( log|V| )
- 第一步會檢查所有邊以更新每個連通分量的最小弧
- O( |E| )
- 時間複雜度
- O( log|V|×|E| )
Shortest path problem
Dijkstra's algorithm | Bellman-ford algorithm | Folyed-warshall algorihm | |
---|---|---|---|
欲解問題 | Single source | Single source | All pair shortest |
策略 | Greedy | Dynamic programming | Dynamic programming |
允許負邊? | NO | YES | YES |
允許負環? | NO | NO | NO |
時間複雜度 | Adjacency matrix: O(\(V^2\)) | Adjacency matrix: O(\(V^3\)) | O( \(V^3\) ) |
Adjacency list Heap:O(|E|log|V|) Fib. heap:O(|V|log|V|+|E|) |
Adjacency list O(|V|×|E|) |
Dijkstra's algorithm [DS 書上版本]
適用於有向圖或無向圖皆可以
Data structure
- Cost matrix:為一 n × n 的矩陣,n = |V|
\(cost[i, j] = \left\{\begin{matrix} 邊成本值 & ,if \;<i,j> \in E \\ 0 & ,if \; i=j \\ \infty & ,if \;<i,j> \notin E\end{matrix}\right.\)
- S[1...n]:Boolean
\(S[i] = \left\{\begin{matrix}False & , 起點到 \;i \;點的最短距離尚未決定 \\True & , 起點到 \;i \;點的最短距離已決定\end{matrix}\right.\)
- Dist[1...n]:Integer
Dist[i] : 起點到 i 點的最短路徑長(初值為「Cost matrix」起點到該點值)
- 概念
1 | if(Dist[i]>Dist[u]+Cost[u,i]) { |
圖中不可有負邊,否則無法求初正確的最短路徑
以(1)作為起點,使用「Dijkstra's algorithm」則
Dist[2] = 5
,但其實應為Dist[2] = 2
不得對所有邊以加權種方式使得負邊為正,因為若有一路徑經過點越多,則該路徑權重也倍數成長,如下:
在路徑<1,2>路徑上只有增加權重 6 ,但是在路徑<1,3,2>上路徑增加權重為 12
Example
- Algorithm
1 | // s: 起點 |
- Time complexity
- Binary heap:O(|V|) + O(|V|) + O(|V|log|V|) + O(|E|log|V|) = O(|E|log|V|)
- Fibonacci heap:O(|V|) + O(|V|) + O(|V|log|V|) + O(|E|) = O(|V|log|V|+|E|)
- 等同於「Prim's algorithm」
- 與「Prim's algorithm」的時間複雜度相同
- 「Prim's algorithm」與「Dijkstra's algorithm」都採用「廣度優先搜尋法」
Bellman-Ford algorithm
Data structure
- \(Dist^k[1...n]\):起點到 i 點的最短路徑長且經過邊數 ≦ k
- \(Dist^1\) :初值,「Cost matrix」之起點到各點的權重值
- 依序求出 \(Dist^2、Dist^3、\ldots、Dist^{n-1}\),且 \(Dist^{n-1}\) 即為最後結果
\(Dist^k[i] = min_{\forall u}{Dist^{k-1}[i], min{Dist^{k-1}[u]+Cost[u, i]}}\),u 為有邊指向 i 的頂點
Example
- Algorithm
1 | // Cost: cost matrix |
採用「Adjacency list」時間複雜度為 O(|V|×|E|) = O(|V|×|\(V^2\)|)
Example
如何利用「Bellman Ford algorithm」判斷圖上是否有負環?
- 先執行完一次正常的「Bellman Ford」(求到 \(Dist^{n-1}\) 為止)
- 再求 \(Dist^n\),若有值比 \(Dist^{n-1}\) 還小,則必存在負環
1 | for(int i = 1; i <= n; i++) { |
Example(102交通大學資料結構與演算法 - 差分約束系統(System of Difference Constraints))
- Consider the problem of finding a vector \((x_1, x_2, x_3, x_4, x_5)\) satisfying the following constrains such that \(x_1+x_2+x_3+x_4+x_5\) is maximized, where \(x_i \leq 0\) for i = 1, ..., 5. What are the maximum value and the corresponding vector?
\(x_1 - x_2 \leq 1 \\x_1 - x_5 \leq -1 \\ x_2 - x_5 \leq 1 \\ x_3 - x_1 \leq 15 \\ x_4 - x_1 \leq 4 \\ x_4 - x_3 \leq -1 \\ x_5 - x_3 \leq -3 \\ x_5 - x_4 \leq 0\)
在此條件下求最大解可以轉換成「Single source all shortest path problem」
- 觀察 \(x_j - x_i \leq b_k\)
- 圖論中, d[v] ≦ d[u]+w[u,v]
- d[v]:為 s 至 v 的最短路徑長
- d[u]:為 s 至 u 的最短路徑長
- w[u,v]:邊(u,v) 的權重
- 所以此式意旨「s 至 v 的最短路徑長」≦「s 至 u 的最短路徑長」+「邊(u,v) 的權重」
- 將 \(x_j - x_i \leq b_k\) 視為 d[v] - d[u] ≦ w[u,v]
- 其中 s 為額外增加的節點
- 圖論中, d[v] ≦ d[u]+w[u,v]
- 如果 s 點無法到達 \(x_i\)
- 則其路徑為無窮大
- 因為不斷作「Relaxation」會使得路徑不斷變小,直到滿足所有條件時其為最大可能
- 對 s 頂點使用「Bellman-Ford algorithm」
- 解出 \((x_1, x_2, x_3, x_4, x_5) = (-4, -2, 0, -1, -3)\)
- 總和:-10
在此條件下求最大解可以轉換成「Single source all longest path problem」
- 觀察 \(-(x_j - x_i) \geq -b_k\)
- 圖論中, d[v] ≧ d[u]+w[u,v]
- d[v]:為 s 至 v 的最長路徑長
- d[u]:為 s 至 u 的最長路徑長
- w[u,v]:邊(u,v) 的權重
- 所以此式意旨「s 至 v 的最長路徑長」≧「s 至 u 的最長路徑長」+「邊(u,v) 的權重」
- 將 \(x_i - x_j \geq -b_k\) 視為 d[v] - d[u] ≧ w[u,v]
- 其中 s 為額外增加的節點
- 如果 s 點無法到達 \(x_i\)
- 則其路徑為無窮小
- 因為不斷作「Relaxation」會使得路徑不斷變大,直到滿足所有條件時其為最小可能
\(x_2 - x_1 \geq -1 \\x_5 - x_1 \geq 1 \\ x_5 - x_2 \geq -1 \\ x_1 - x_3 \geq -15 \\ x_1 - x_4 \geq -4 \\ x_3 - x_4 \geq 1 \\ x_3 - x_5 \geq 3 \\ x_4 - x_5 \geq 0\)
對 s 頂點使用「Bellman-Ford algorithm」(Single source all longest path)
Floyd-Warshell algorithm
假設 G = <V, E>
Data structure
- \(A^k\) :n × n 矩陣,n = |V|
- \(A^k[i, j]\):i 到 j 之最短路徑且途中經過頂點的編號必須 ≦ k
- \(A^0\):原始的「Cost matrix」,依序求出 \(A^1, A^2, \ldots, A^n\)
概念
\(A^k[i,j]= min{A^{k-1}[i,j], A^{k-1}[i,k]+A^{k-1}[k,j]}\),針對點(k)作討論,如果使用此頂點作為中介點路徑是否可以縮短
Example
- Algorithm
1 | // Cost[1..n, 1..n]: 邊的權重集合 |
Johnson's algorithm
All pair shortest path problem for sparse graph
- 一個 n 個頂點的無負邊有向圖上,可以在每個點上做一次「Dijkstra's algorithm」以求出「All pair shortest path」
- 時間複雜度(All pair shortest path)
- [DS 版本:鄰接矩陣] |V| × O(\(|V|^2\)) = O(\(|V|^3\))
- [Algorithm 版本:鄰接串列] |V|×O(|E|log|V|)(Binary heap);|V|×O(|V|log|V|+|E|)(Fibonacci heap)
- 一個 n 個頂點的有負邊有向圖上,可以在每個點上做一次「Bellman-Ford's algorithm」以求出「All pair shortest path」
- 時間複雜度(All pair shortest path)
- |V|×O(|V|×|E|) = |V|×O(|\(V^3\)|) = O(|\(V^4\)|)
「Johnson's algorithm」藉由融合「Bellman-Ford's algorithm」可以對負邊進行運算的優點,與「Dijkstra's algorithm」在邊數很少時可以讓複雜度降低的優點,所以在「Sparse graph」計算「All pair shortest path」時,可以因為邊數少而降低時間複雜度,又不因負邊而無法計算正確的數值
- 演算法概念
- 以「Bellman-Ford's algorithm」解決負邊問題
- 使用「Dijkstra's algorithm」計算無負邊圖的「Shortest path」
給定一圖 G = <V,E>
步驟:
- 加入一個新的頂點 s 與 G 上的每個點連接成為新的圖 G' = <V', E'>,且每條新增的邊之權重為 0
- 對 s 頂點執行「Bellman-ford's algorithm」(Single source shortest path)
- 令 s 到其他 G 上的頂點 v 之「Shortest path」為 h(v)
- 對 G' 上每個邊進行「重設權重」(Reweight)
- \(\hat{w}(u,v) = w(u,v) + h(u)-h(v)\)
- 將 G' 上 s 點與連接的邊移除,保留「重設權重」的邊,對剩下的每個頂點做「Dijkstra's algorithm」
時間複雜度:(等價於對每個頂點做「Dijkstra's algorithm」,只是需要前置處理)
- $O(|V|) + O(|V||E|) + O(|E|) + |V|O(|V| |V|+|E|) = O(|V|^2 |V|+|V||E|) $
- (新增|V|條邊)+(修正負邊)+(重設權重)+(對每個頂點做「Dijkstra's algorithm」)
Example(證明「Johnson's algorithm」對邊的修正必不會產生負邊)
考慮一個「強連通有向圖」G = <V,E>,並且圖上存在負邊但無負環路
假設 c(u,v) 表示 u 到 v 頂點的權重
假設 d(u,v) 表示 u 到 v 頂點的最小權重
證明 c(w,v) + d(u,w) - d(u,v) ≧ 0,對於圖上任意三個相異頂點
令 u 到 v 頂點的「Shortest path」為 d(u,v) ,所以任何其他從 u 到 v 頂點的路徑必 ≧ d(u,v),且 d(u,w)+c(w,v) 亦為一個從 u 到 v 頂點的路徑,所以:
\(d(u,w) + c(w,v) \geq d(u,v) \Rightarrow c(w,v) + d(u,w) - d(u,v) \geq 0\)
Example(證明「重設權重」的圖 G',其計算的「All pair shortest path」與圖 G 計算相等)
考慮一個「強連通有向圖」G = <V,E>,並且圖上存在負邊但無負環路
假設 c(u,v) 表示 u 到 v 頂點的權重
假設 d(u,v) 表示 u 到 v 頂點的最小權重
新增一頂點 s 並對各個頂點連接且權重為 0,將每個邊以 c(u,v) + d(s,u) - d(s,v)(c(u,v)+h(u)-h(v))的數值重設權重後成為 G',證明其「Shortest path」在 G 上亦成立
考慮 P = <\(u = v_0, v_1, \ldots, v = v_k\)> 為一條從 u 到 v 的一條路徑
令 \(\hat{w}(u,v) = c(u, v)+d(s,u) - d(s,v)\),則該路徑的權重總和為:
\(\hat{w}(P) = \sum_{i = 1}^k \hat{w}(v_{i-1}, v_i)\)
將重設的權重值代入:
\(\Rightarrow \sum_{i = 1}^k [c(v_{i-1},v_i)+d(s,v_{i-1}) - d(s, v_i)] = \sum_{i = 1}^k [c(v_{i-1},v_i)+h(v_{i-1}) - h(v_i)]\)
展開相消:
\(\Rightarrow (\sum_{i=1}^kc(v_{i-1}, v_i)) + h(v_0) - h(v_k) = w(P)+h(v_0)-h(v_k)\\ \Rightarrow \hat{w}(P) = w(P)+h(v_0)-h(v_k)\)
因為 \(h(v_0)\) 與 h(\(v_1\)) 為固定值,所以當 \(\hat{w}(P)\) 愈小時,等價於 \(w(P)\) 也隨之愈小,且不會因為路徑長短而讓修正的邊影響答案(見下例)
Example(失敗的「Reweight」方法)
- 給定一圖 G = <V,E>,而 P 為一條從 s 到 t 頂點的「Shortest path」,假設將 G 上的每條邊的權重減一成為 G',請問是否 P 路徑仍為 s 到 t 在 G' 中的「Shortest path」?
- 給定一圖 G = <V,E>,而 P 為一條從 s 到 t 頂點的「Shortest path」,假設將 G 上的每條邊的權重改為 \(\hat{w}(u,v) = w(u, v) - w^*\) 成為 G',請問是否 P 路徑仍為 s 到 t 在 G' 中的「Shortest path」?
- \(w^* = min_{(u,v)\in G.E}{w(u, v)}\)
(1)否,因為若路徑越長,權重扣除的越多:
從 u 到 v 頂點的原始「Shortest path」為 <u, v>
因為路徑 <u,x,y,v> 比路徑 <u,v> 還要長,所以扣除的權重也越多(每條邊的權重扣 1),所以使 <u,x,y,v> 成為 u 到 v 的新「Shortest path」
(2)否,概念與上題一致:
從 u 到 v 頂點的原始「Shortest path」為 <u, x, y, v>
因為路徑 <u,x,y,v> 比路徑 <u,v> 還要長,所以扣除的權重也越多(每條邊的權重扣 -5;應該說增加權重),所以使 <u,v> 成為 u 到 v 的新「Shortest path」
Activity on vertex network and topological sort
令 G = <V,E> 為有向圖,若為一個「Activity on vertex network」,則:
- Vertex:代表「工作」(Activity)
- Edge:工作之間的「先後次序關係」
- 若(頂點 i)→(頂點 j),代表「工作(i)必須要在工作(j)之前作完」
- 應用
- 判斷計畫工作先後的執行是否合理,即至少要有 ≧ 1 組合理的工作順序(使用拓樸排序來判斷)
- Topological sort(Order)
- 給一個不具有環路(使用 DFS 檢查是否有「Back edge」)的「AOV network」,則至少可產生 ≧ 1 組「頂點的拜訪順序」,且若「AOV network 中(i)→(j)」,則最後輸出的拜訪順序中,(i)必定出現在(j)之前
步驟:
- 找出「In-degree」等於 0 的頂點(i)
- 輸出該點,並將(i)點指出去的邊從圖中移除
- 重複第一、第二步直到所有頂點皆已輸出,或是圖上無「In-degree」等於 0 的頂點
- 確認是否所有頂點皆輸出,若否表示該圖上無法建立一個「Topological sort」(因為該圖上有環路)
若該圖不含有環路,則「Topological sort」 ≧ 1 組;反之,則無法建立「Topological sort」
Example
- Algorithm [Algorithm 書上版本]
1 | int DFS_visit(G, u, p) { |
Example
- Time complexity
- (等同於 DFS 的時間複雜度)O(|V|+|E|)
Topological sort(DFS 應用)
在「有向圖」上找出「Strongly connected component」
- Algorithm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 int DFS_visit(G, u, p) {
time++;
u.color = gray;
u.d = time;
for each w in G.adj[u] {
if(w==white) {
w.parent = u;
DFS_visit(G, w, p);
}
}
time++;
u.color = black;
u.f = time;
insertFront(u, p); // 當一個頂點完成了 DFS 的搜尋,
// 將該頂點插入連結串列的前面
}
int DFS_visit(G, u) {
time++;
u.color = gray;
u.d = time;
for each w in G.adj[u] {
if(w==white) {
w.parent = u;
DFS_visit(G, w, p);
}
}
time++;
u.color = black;
u.f = time;
}
void DFS(G, p) {
// O(|V|)
// Initialize
for each u in G.V {
u.color = white;
u.parent = null;
}
time = 0;
for each u in G.V { // ↑
if(u.color==white) { // |
DFS_visit(G, u, p); // | O(|V|+|E|)
} // |
} // ↓
}
graph Stronglyconnectedcomponent(G) {
ptr p = null;
reverse(G_T, G); // 將原本 G 上的邊<u,v>,改為邊<v,u>
DFS(G, p);
// Initialize,要對 G_T 作 DFS 的事前準備
for each u in G_T.V {
u.color = white;
u.parent = null;
}
time = 0;
for(ptr i = p; i->link != null; i = i->link) {
// 沿用「Topological sort」的鍊結串列,
DFS_visit(G_T, i); // 因為此順序恰巧是「Finish time」由大到
} // 小排序下來,依照此順序對 G_T 作 DFS
return G_T; // 在 G_T 上的 DFS forest 即為各個
// 「Strongly connected component」
}Example
Topological sort + Strongly connected component
(判斷「Semi-connected graph」)
Example(106 成功大學程式設計)
- A directed graph G=(V,E) is semi-connected if, for all pairs of vertices u,v ∈ V, u is reachable from v through a directed path, or v is reachable from u through a directed path. Given a linear-time algorithm to determine whether or not G is semi-connected.
用「Topological sort」找出 G 的所有「Strongly connected component」(SCC)
- 將每個 SCC 視為一頂點組成集合 V'
- 假設共有 k 個 SCC
- 各個 SCC 在 G 相連的邊組成集合 E'
- \(E'={(V_1,V_2) ∣ \exist v_1 \in V_1 , v_2 \in V_2 \ni (v1,v2) \in E)}\)
令 G'=(V',E')
- 必為「Directed acyclic graph」(DAG)
- 若兩點存在環路
- 則該兩點必成為一個 SCC
- 對 G' 執行「Topological sort」
- 找出序列 \(T=(v_1,\ldots,v_k )\)
- 若 \((V_i, V_{i+1}) \in E' ,i = 1,\ldots,k-1\) 成立
- G 為「Semi-connected graph」
正確性證明
假設 G=(V,E) 為「Semi-connected graph」
- 對所有 \(v_1, v_2\in V\) 存在路徑
- 不失一般性假設為 \((v_1\rightarrow \ldots\rightarrow v_2)\)
- 假設 \(v_1 \in V_1, v_2 \in V_2\)
- \(V_1, V_2\) 為 G 的兩個 SCC
- 若 \((V_1, V_2)\in E'\)
- \(v_1, v_2\) 必有路徑
有向無環路圖(Direct acyclic graph)上求「Single source all shortest paths」
可以利用「Topological sort」(O(|V|+|E|))來減少維護「Fibonacci heap」需要的時間複雜度(每回合的Delete-min:O(|V|log|V|))
- Algorithm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 // s: 起點
Initialize(G, s) { // ↑
for each vertex v in G.V { // ︳
v.d = ∞; // ︳
v.parent = null; // ︳ O(|V|)
} // ︳
s.d = 0; // ︳
} // ↓
Relax(u,v,W) {
if(v.d > u.d+W[u,v]) {
v.d = u.d + w[u,v];
v.parent = u;
}
}
// W[1..n,1..n]: 邊權重集合
// s: 起點
void DAG_shorestpath(G,W,s) {
ptr p = Topologicalsort(G);
// Dijkstra's algorithm
Initialize(G, s);
for(ptr u = p; u->link != null; u = u->link) {
for each vertex v in G.adj[u] {
Relax(u,v,W)
}
}
}Example
- Time complexity
- O(|V|+|E|) + O(|V|) + O(|V|) + O(|E|) = O(|V|+|E|)
- 「Topological sort」+「Initialization」+「Trace adjacency list」
Activity on edge network, critical path and critical task
應用於「計畫的專案管理」、CPM(Critical path management)
G = <V,E> 為有向圖且為「AOE network」,則:
- Vertex:代表「事件」(Event)、「里程碑」(Milestone)、「查核點」
- Edge:代表「工作」(Activity)
- 權重:代表該「工作」需要完成的工作時數
所有指向某「事件頂點」的工作全部完成時,該事件才會發生
「事件頂點」一旦觸發後,從此頂點指出的「工作邊」才可以開工
Example
- 完成計畫最快需要幾天?
- 求 s 至 e 之最常路徑,也就為「Critical path」的長度(24天)
- 列出所有「Critical path」
- <s,e>路徑長為 24 的皆為「Critical path」
- 列出所有「Critical task」(不可延遲的工作)
- 在「Critical path」上的工作邊{\(a_1, a_6, a_7, a_8, a_{10}, a_{12}\)}
- 那些工作(瓶頸)的時數縮短後,可有效的縮短整體計畫之工作時數?
- 所有「Critical path」上的「共同工作」(交集邊){\(a_1, a_6, a_{12}\)}
- 那些工作可以延遲?可以延遲多久不影響進度?
- 不在「Critical path」上的工作邊可以有延遲
- 求各個事件頂點點之「最早觸發時間」(起點至各點的「最長路徑長」)
事件頂點 s t u v w x e 最早觸發時間點 0 8 13 16 19 12 24 最晚觸發時間點
- 求各個事件頂點點之「最晚觸發時間」(終點回推各點的最小值)
綜合以上可以發現事件頂點 x 必定要在第 7 天觸發
查看指向自己的頂點以確認最晚觸發的時間,也可以確認該工作邊是否可以延遲
事件頂點 s t u v w x e 最早觸發時間點 0 8 13 16 19 12 24 最晚觸發時間點 0 8 13 16 19 14 24 事件頂點 x 的觸發可以延遲
- 求各個工作之「最早開工」與「最晚開工」時間點
- 最早開工等價於「需觸發之事件點」之「最早觸發」時間點
- 最晚開工等價於「欲觸發之事件點」之「最晚觸發」時間點
工作邊 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 最早開工時間點 0 0 0 8 8 8 13 13 12 16 12 19 最晚開工時間點 0 8 9 10 9 8 13 13 14 16 19 19 在「Critical path」上的工作邊{\(a_1, a_6, a_7, a_8, a_{10}, a_{12}\)}
Articulation point
Example
橘色即為「Articulation point」
Biconnected graph
一個連通無向圖,且無「Articulation point」,則稱為「Biconnected graph」
- 應用
- 網路節點設置
- Biconnected component
- 子圖
- 連通
- 為極大的「Biconnected」子圖
- 求出「Articulation point」(以上圖為例)
- 從任意頂點作「深度優先查找」(這裡作DFS(3)),求出各點的「DFN」(DFS拜訪順序)
頂點 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
DFN | 3 | 2 | 4 | 1 | 5 | 6 | 7 | 8 | 9 | 10 |
LOW |
畫出「DFS spanning tree」並標出「Back edge」
- 求個頂點的「low」值
- \(low(x) = min{dfn(x), {dfn(y)|為x的後代頂點最多經過一條「Back\;edge」所到之頂點}}\)
頂點 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
DFN | 3 | 2 | 4 | 1 | 5 | 6 | 7 | 8 | 9 | 10 |
LOW | 3 | 1 | 1 | 1 | 1 | 6 | 6 | 6 | 9 | 10 |
- 判斷「Articulation point」
- 針對樹根頂點:若有 ≧ 2 個子頂點,則必為「Articulation point」
- (3)有 ≧ 2 個子頂點,所以為「Articulation point」
- 針對非樹根頂點:任意一個 x 之子頂點 y(非所有後代頂點)只要符合
low(y) >= dfn(x)
,則為「Articulation point」- (1)之子頂點(0):
dfn(1)<=low(0)
,為「Articulation point」 - (2)之子頂點(4):
dfn(4)>low(2)
,非「Articulation point」 - (5)之子頂點(6):
dfn(5)>low(6)
,非「Articulation point」 - (6)之子頂點(7):
dfn(6)>low(7)
,非「Articulation point」 - (7)之子頂點(8)、(9):
dfn(7)>low(8)&&dfn(7)>low(9)
,非「Articulation point」
- (1)之子頂點(0):
- 針對葉頂點:皆非「Articulation point」
- (0)、(4)、(8)、(9)
- 針對樹根頂點:若有 ≧ 2 個子頂點,則必為「Articulation point」