給定一張圖,要如何讀取當中的資訊呢?簡單來說就是從一給定點開始,依某種順序去拜訪相鄰(有關係)的點,最後走完所有點,收集完圖中的資訊。以下簡介兩種簡本的 traversal 方法以及應用。
Breadth-First Search (BFS)
從某一節點開始,依序走訪所有相鄰且未拜訪過的節點,由走訪的節點進行先廣後深的搜尋(搜尋會像同心圓般地擴散開來),通常以 queue 來實做。
psuedo code
def bfs(G,s):
Q = Queue(s); # put start into queue
time = 0 # 紀錄節點離開queue的時刻
leave_time[s] = time
visited[s] = True
while Q is not empty:
time++
u = dequeue(Q)
leave_time[u] = time
for v in adj(u):
if visited[v] is False:
visited[v] = True
Q.push(v)
上圖迴圈中的 if
比較會執行 $\mathcal{O}(\text{Total size of adj. list})$,至多 $2|E|$,\
所以total 複雜度為$\mathcal{O}(V+E)$ (把圖讀進來)。\
觀察 leave_time
可以發現 BFS 會優先走過離起點最近的點,而這個特性可以幫助我們解決一些圖論上的問題(e.g 找出每一點到根節點的最小距離)。
Depth-First Search (DFS)
從某一節點開始,探索相鄰的節點,並儘可能深地探索,直到該節點下的所有子孫節點全部遍歷過後,再 backtrace 回前一個節點,重複此步驟直到遍歷全部的節點。通常以 stack 來實做,亦即recursive call 某 function。其中,以 DFS 產生的搜索生成樹,我們稱之為 DFS tree 。
psuedo code
def dfs(G,v):
mark v as visited
for u in adj(v):
if u is not visited:
dfs(u)
複雜度為 $\mathcal{O}(V+E)$
根據拜訪的先後順序,我們可以定義幾種 order ,且這些 order 實務上能用來解決問題,\
比如 pre-ordering
(拜訪的先後順序)、post-ordering
(節點從 stack 離開的先後順序)。
一些相關定義
在 DFS 有向圖的過程中,我們將所遇到的邊進行分類
- Tree Edge: DFS Tree 上的邊
- Back Edge: 連回 DFS tree 上祖先的邊
- Forward Edge: 連接 DFS Tree 上子孫的 non Tree Edge
- Cross Edge: otherwise , 在 DFS Tree 上的一顆子樹連接另一棵子樹的邊
Note: 後兩者在無向圖中不存在($\because$ Back Edge 和 Forward Edge 角色變為一樣;且 Cross Edge 若存在,那麼其必可直接連至另一子樹繼續 dfs ,變為 Tree Edge ,故不可能存在)
General DFS
考慮圖 $G$ 不是 connected 的情況,定義 general DFS 來遍歷個節點,同時每個節點在 DFS 時,引進兩個全域變數 $t,s$。
t=0
s=NULL
def dfs(v):
mark v as visited
for u in adj[v]:
if u is not visited:
dfs(u)
leader(v) = s
leave_time(v) = t
t++
for v in G(V):
if v is not visited:
s=v
dfs(v)
Application - Finding Strongly Connected Component
關於連通的一些定義
- $u$ is connected to $v$ $\Leftrightarrow$ $\exists$ a path from $u$ to $v$
- A connected component of an undirected graph: maximal set of connected vertices\
(已經無法再增添新的節點,並同時保持兩兩節點 connected 的關係) - An undirected graph is connected $\Leftrightarrow$ there is exactly 1 connected component
- A Tree is an connected undirected graph with no simple cycles
- Strongly Connected Component (SCC) of a directed graph:\
A maximal set $S$ of vertices s.t $\forall u,v \in S, u , \text{is connected to}, v$
SCC 的用途
將任意有向圖收縮成 DAG,化簡問題。
Example: [待補]
Kosaraju's Algorithm
整體概念就是先對反圖 $G^{rev}$ 做 general DFS,標註每個節點的finish_time (可以想成把 connected info 擷取出來),接著再依該時間戳記,由高者對 $G$ 做 general DFS,每次遍歷的一群即為一個 SCC。
- Let $G^{rev} = (V,E^{rev}), \text{其中} , (u,v) \in E \Leftrightarrow (v,u) \in E^{rev}$
- Run general DFS on $G^{rev}$
- Run general DFS on $G$, Starting from nodes with highest f(v) \
if leader(u) = leader(v) $\Rightarrow$ $u,v$ in same SCC
一些觀察
不難發現到,反圖與原圖的 SCC 是一樣的 (可以從 SCC 的定義中得出),在 run general DFS 第一遍 (於 $G^{rev}$)時,每個節點的 leader 其實已經設定好,但這並不能用作最終答案,因為此時 $leader(v) = u$ 的意義為在 $G^{rev}$ 上,可以由$u$ 出發到達的點 (也就是在 $G$ 上,由誰出發可以到達 $u$),但不能確定在原圖上從 $v$ 出發可以到達 $u$ ,所以才需要再跑第二次 general DFS 來找出 SCC (而依據$f(v)$ ,則確保了我們依循 topological reverse order,不會有跑到下一個 SCC 的情況發生)
psuedo code
def kosaraju(G,SCC_list):
GT = G with reversing edges
general_dfs(GT)
for v in G(V) with decending finish_time stamp in GT:
if v is not visited:
DFS(v,G) with leader = v then make a set S (with every element whose leader is v)
SCC_list.append(S)
Application - Topological Sort
Def. Directed Acyclic Graph (DAG,有向無環圖)
有向圖中的特例,所有的邊都朝著同一個方向延伸,所有節點可定義先後次序(也就是下方定義的拓樸排序) (e.g 玩遊戲時,將所有關卡視為一個個節點,要先破完 A 關卡或 B 關卡,才可以打後續E關卡,每種可能的破關順序即是一種 Topological order)
Topolocial Order (拓樸排序)
在有向圖中,找到一個節點的排序 $v_1,v_2,\cdots,v_n$,使得任意有向邊 $e(v_i,v_j) \in E$, $v_i$ comes before $v_j$ in the ordering 。
DFS Algortihm
Run DFS on $G^{rev}$ \
$f(v)$ 由小到大的順序即為 Topological order,複雜度
$\mathcal{O}(V+E)$\
Note: 若是 run 在 $G$ 上,那麼 $f(v)$ 由大到小的順序亦是 Topological order
Example
範例: LeetCode 210: Course Schedule II
comments powered by Disqus