談到算法,圖的操作是避免不了。
而我們一般談到圖時,又必定會談到圖的遍歷。
圖的遍歷通常有 2 種,深度優先(DFS) 和廣度優先(BFS)。
本篇博文講解深度優先(DFS)。
圖的表示
圖有兩種表示方式
1. 臨接矩陣
其實就是一個權重矩陣,用 1 代表兩個結點有連接,0 表示沒有連接,這樣的表示方式通俗易懂,特別適合稠密圖,也就是大多數結點是亮亮連接的情況。
2. 臨接表
用一個數組儲存所有的頂點的信息,每個頂點又用一個鏈表或者是數組存放與它相臨的結點的信息。
這樣的表示方式特別適合稀疏圖,也就是比較少的結點之間相互有連接。
本文示例代碼用 Python 表示,爲了簡便,用臨接表這種形式表示
DFS 算法思路
其實 DFS 的思路非常簡單。
如果你哪天錢包忘記在哪裏了,以 DFS 的思路就是,一個房間一個房間找。
先選定一個房間,大致掃一眼,發現沒有。
然後,就選擇房間裏面的辦公桌,桌子上沒有的話,再去桌子上的櫃子裏面找。
如果桌子沒有,就去牀上着。
如果翻遍了所有的角落,也沒有那就換個房間。
DFS 圖例
上面是一張圖,如果要遍歷圖中所有的結點,又不重複。
可以先選擇一個頂點作爲根結點,然後沿着路徑一條一條遍歷下去。
關鍵詞是遞歸,因爲遞歸需要終止條件,所以另外需要用一個數組記錄已經訪問過的結點,當一個結點它的臨結點都已經訪問時,它就需要回溯,這樣能避免重複及陷入死循環。
我們首先選擇 A.
A 有 2 個臨接結點 B 和 C,我們選擇先從 B 出發,所以此時路徑是
A--->B
現在在 B 結點位置,B 有 3 個臨接結點,C、D、F,按照優先往右邊走的原則,我們選擇 F,所以此時的路徑是
A--->B--->F
F 也有 3 個臨接點B,D,E,按照從最右邊的順序遍歷,我們選擇 E,現在路徑是:
A--->B--->F--->E
同樣的邏輯,在 E 點會選擇右邊的 C,這時候路徑是
A--->B--->F--->E--->C
到達 C 點時,情況有些不同,它的臨接點 A 和 B 都已經訪問過了,代表這條路徑到頭了,需要向上回溯。
回溯的過程也是順着路徑往回撤,路徑是
A--->B--->F--->E--->C--->E
E 有 2 個臨接點,因爲 C 點已經訪問了,所以 E 可以訪問 D.
到了 D 點,D 的臨接點都已經訪問過,所以 D 也需要往回溯,這種回溯是遞歸的,最終會回溯到節點 A.
A 是從 B 出發的,按照算法邏輯,這個時候應該從 C 出發了,但是 C 已經被訪問了,所以最終整個遍歷就結束了。
Python 代碼
dfs.py
# G 是臨接表的方式表達圖形
G={}
G[0] = {1,2}
G[1] = {2,3,5}
G[2] = {0,1,3}
G[3] = {2,4,5}
G[4] = {1,3,5}
G[5] = {1,3,4}
# 記錄訪問過的結點
visited = [False,False,False,False,False,False]
# 結點的別名
names=['A','B','C','D','E','F']
def dfs(v):
# 打印訪問的點
print("--->",names[v])
visited[v] = True
for i,value in enumerate(G[v]):
# 如果可以訪問,最沿着路徑一直訪問
if not visited[value]:
dfs(value)
if __name__ == "__main__":
# 打印圖
print("Graphy:",G)
dfs(0)
最終結果如下:
Graphy: {0: {1, 2}, 1: {2, 3, 5}, 2: {0, 1, 3}, 3: {2, 4, 5}, 4: {1, 3, 5}, 5: {1, 3, 4}}
---> A
---> B
---> C
---> D
---> E
---> F
可以看到,代碼可以正常執行,大家親手實踐一下吧。