一個圖 (Graph) 定義為 ,其中 代表頂點 (vertices) 之集合; 代表邊 (edges) 之集合
兩頂點 , 若相連,表示為
圖中邊集合 中元素若為無序對,則稱 為無向圖 (Undirected Graph);反之,若為有序對,則稱 為有向圖 (Directed Graph):
我們定義一條自 到 之 path 為一序列
此序列中任相鄰之兩頂點 , 均存在邊 使之相連
給定 ,稱兩頂點 為 reachable 若且唯若存在一條從 到 之 path
之子圖 亦為一圖,滿足 且
給定 為有 個頂點、 個邊之圖,那麼寫程式時該如何表達圖呢?
用一個 的二維陣列 A
,若 , 為 reachable 則 A[i,j] == 1
;反之,則 A[i,j] == 0
若把二維陣列看作矩陣,則無向圖之 adjacency matrix 必為 symmetric matrix
Adjacency matrix 需要 的空間,適用於邊數多的圖 (dense graph)
用一個長度 n
的陣列,其中每格代表 中對應的頂點,陣列元素裝該與頂點相連的頂點所構成的 linked list
需要 的空間,適合邊少的圖 (sparse graph)
雖然我上面 adjacency matrix 是給 vector<std::vector<int>>
而 adjacency list 我是給 vector<std::list<int>>
但其實通常在 C++ 中要表示 adjacency list 還是更習慣使用 vector<std::vector<int>>
畢竟每列 vector 長度可變,我們只要裝填元素的邏輯是清楚的就好
很像 adjacency list ,不過我們對長度 n
的陣列裝的東西有所不同,改試著裝邊 (邊節點):
Edge Node (可有可無) | V1 | V2 | Link for V1 | Link for V2 |
有向圖 的 incidence matrix 是一個 的矩陣滿足:
如果是無向圖:
陣列前 項是 index 用來表示陣列中從那開始是記錄與該頂點相鄰的頂點,後面則全都紀錄相鄰頂點
很少考,但考出來你要記得上述規則,不然你會看不出圖長怎樣
接下來是圖論中相當頻繁使用的兩個演算法,用在圖之遍歷:
在演算法中,因為它有要說明別的東西,所以多了些標示及定義,實際上像是 LeetCode 刷題時,則如資料結構中那樣直接寫
假設 為一個 graph, 為一個 source vertex ,則 :
nil
(在 BFS 中, 是被 所發現的)
3. 為 BFS 所求出,自 到 之距離
4. 為自 到 所需經過之最少邊數,
5.
Time complexity:
( 我們使用 adjacency list 來表示圖,如果是 adjacency matrix,則會是 )
BFS 只能找出所有與 為 reachable 的頂點,即 所在之 component 中的所有頂點
所以要注意,依此定義不見得能遍歷圖中所有頂點
也因此, BFS 可以用來判斷 是否為連通圖
假設 , 為 中距離最遠的兩頂點,則該兩點之距離即為 diameter ()
在這邊,可以延伸到兩件事:
關於 tree 上找 diameter 的演算法可參考下列:
與 BFS 不同的是,對於搜尋到的頂點 ,我們會紀錄其發現時間 ,接著對其在遞迴樹上的 child 繼續遞迴搜尋,直到後代都被搜尋到才結束對 的的搜尋,並且紀錄該結束時間
也由此可知,在此定義下的 DFS 能遍歷 中所有的頂點
Time complexity:
Note
雖然上面的 code 是基於無向圖,但我們接著的討論會先基於有向圖
以此定義執行完 DFS ,我們可以根據 depth-first forest 將 中所有 分為以下四類:
依據我們 DFS 執行完後的 ,此時顏色就派上用場了
此外,
如果 是無向圖,則想想會發現,forward edge 可以想成 back edge 的反向而 cross edge 可以想成 tree edge 的反向
故如果 為無向圖則 中僅含 tree edge 與 back edge
此外,不難想像, is acyclic (經過 DFS 後)不含 back edge
acyclic 表示 沒有 cycle,讀者如果想像不出來,可以試著用反證法去證明看看
常考的應用:
Important
值得注意的是,上面的 algo 在有向圖不會有問題,但在無向圖中,判斷是否為 back edge 還要多一個條件是 並非 之 parent (g.vertices[v].color == Color::GRAY && g.vertices[u].pi != &g.vertices[v]
)
Tip
對無向圖 做 DFS 的過程中如果偵測到 back edge ,則 含有 cycle
若走完 個邊後尚有邊未被走訪,表示 中含有 cycle
也因此對於無向圖,偵測是否含有 cycle 的演算法之時間複雜度為 ,與 無關
給定一個有向圖 ,若 為 中元素之一種排序方式滿足:若 則在 中 出現在 的前面,則稱 為一個 topological sort
由此定義可知對於有向圖 ,存在 之 topological sort 若且唯若 is acyclic
所以,給定 DAG ,對其執行 DFS 後,無論 為 tree edge, forward edge 還是 cross edge ,皆可推得
故我們找出 topological sort 的演算法只需要對 DFS 後所有頂點的 finishing time 做排序即可
給定有向圖 ,若 為 maximal 滿足: path 自 走到 且亦 path 自 走到
則稱 為一個 strongly connected component (SCC) of
如果給定有向圖 ,建立一個新圖 稱之為 component graph
顧名思義, 中元素對應 中的 components
則 使得 中存在自 到 的 path
這樣的 component graph 必為 DAG
此時離想出演算法只差最後的巧思:我們對 中所有邊取反向建成新的
我們先對 做 DFS 再對 做 DFS,過程中依照第一次 DFS 求出的 finishing time 由大到小選點,則最後所得的 depth-first forest 中每棵 tree 即一個 SCC
這便是 Kosaraju's algorithm