深度優先搜索(DFS:Depth-First Search)是一種圖搜索策略,其將搜索限制到 2 種操作:
- (a) 訪問圖中的一個節點;
- (b) 訪問該節點的子節點;
在深度優先搜索中,對於最新發現的頂點,如果它還有以此為起點而未探測到的邊,就沿此邊繼續探測下去。當頂點 v 的所有邊都已被探尋過后,搜索將回溯到發現頂點 v 有起始點的那些邊。這一過程一直進行到已發現從源頂點可達的所有頂點為止。實際上深度優先搜索最初的探究也是為了解決迷宮問題。
對圖的深度優先搜索與對樹(Tree)的深度優先遍歷(Depth First Traversal)是類似的,區別在於圖中可能存在環,所以可能會遍歷到已經遍歷的節點。
例如,下面的圖中,從頂點 2 開始遍歷,當遍歷到頂點 0 時,子頂點為 1 和 2,而頂點 2 已經遍歷過,如果不做標記,遍歷過程將陷入死循環。所以,在 DFS 的算法實現中需要對頂點是否訪問過做標記。
上圖的 DFS 遍歷結果為 2, 0, 1, 3。
DFS 算法可以通過不同方式來實現:
- 遞歸方式
- 非遞歸方式:使用棧(Stack)數據結構來存儲遍歷圖中節點的中間狀態;
DFS 算法的遞歸方式偽碼如下:
1 procedure DFS(G,v): 2 label v as discovered 3 for all edges from v to w in G.adjacentEdges(v) do 4 if vertex w is not labeled as discovered then 5 recursively call DFS(G,w)
DFS 算法的非遞歸方式偽碼如下:
1 procedure DFS-iterative(G,v): 2 let S be a stack 3 S.push(v) 4 while S is not empty 5 v ← S.pop() 6 if v is not labeled as discovered: 7 label v as discovered 8 for all edges from v to w in G.adjacentEdges(v) do 9 S.push(w)
深度優先搜索(DFS)的時間復雜度為 O(V+E),V 即 Vertex 頂點數量,E 即 Edge 邊數量。
DFS 算法實現代碼如下:
1 using System; 2 using System.Linq; 3 using System.Collections.Generic; 4 5 namespace GraphAlgorithmTesting 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 Graph g = new Graph(4); 12 g.AddEdge(0, 1); 13 g.AddEdge(0, 2); 14 g.AddEdge(1, 2); 15 g.AddEdge(2, 0); 16 g.AddEdge(2, 3); 17 g.AddEdge(3, 3); 18 19 foreach (var vertex in g.DFS(2)) 20 { 21 Console.WriteLine(vertex); 22 } 23 foreach (var vertex in g.RecursiveDFS(2)) 24 { 25 Console.WriteLine(vertex); 26 } 27 28 Console.ReadKey(); 29 } 30 31 class Edge 32 { 33 public Edge(int begin, int end) 34 { 35 this.Begin = begin; 36 this.End = end; 37 } 38 39 public int Begin { get; private set; } 40 public int End { get; private set; } 41 } 42 43 class Graph 44 { 45 private Dictionary<int, List<Edge>> _adjacentEdges 46 = new Dictionary<int, List<Edge>>(); 47 48 public Graph(int vertexCount) 49 { 50 this.VertexCount = vertexCount; 51 } 52 53 public int VertexCount { get; private set; } 54 55 public void AddEdge(int begin, int end) 56 { 57 if (!_adjacentEdges.ContainsKey(begin)) 58 { 59 var edges = new List<Edge>(); 60 _adjacentEdges.Add(begin, edges); 61 } 62 63 _adjacentEdges[begin].Add(new Edge(begin, end)); 64 } 65 66 public List<int> DFS(int start) 67 { 68 List<int> traversal = new List<int>(); 69 int current = start; 70 71 // mark all the vertices as not visited 72 bool[] visited = new bool[VertexCount]; 73 for (int i = 0; i < VertexCount; i++) 74 { 75 visited[i] = false; 76 } 77 78 // create a stack for DFS 79 Stack<int> stack = new Stack<int>(); 80 81 // mark the current node as visited and push it 82 visited[current] = true; 83 stack.Push(current); 84 85 while (stack.Count > 0) 86 { 87 current = stack.Pop(); 88 89 // if this is what we are looking for 90 traversal.Add(current); 91 92 // get all child vertices of the popped vertex, 93 // if a child has not been visited, 94 // then mark it visited and push it 95 if (_adjacentEdges.ContainsKey(current)) 96 { 97 foreach (var edge in _adjacentEdges[current].OrderByDescending(e => e.End)) 98 { 99 if (!visited[edge.End]) 100 { 101 visited[edge.End] = true; 102 stack.Push(edge.End); 103 } 104 } 105 } 106 } 107 108 return traversal; 109 } 110 111 public List<int> RecursiveDFS(int start) 112 { 113 List<int> traversal = new List<int>(); 114 int current = start; 115 116 // mark all the vertices as not visited 117 bool[] visited = new bool[VertexCount]; 118 for (int i = 0; i < VertexCount; i++) 119 { 120 visited[i] = false; 121 } 122 123 // traversal 124 RecursiveDFSTraversal(current, visited, traversal); 125 126 return traversal; 127 } 128 129 private void RecursiveDFSTraversal(int current, bool[] visited, List<int> traversal) 130 { 131 visited[current] = true; 132 traversal.Add(current); 133 134 if (_adjacentEdges.ContainsKey(current)) 135 { 136 foreach (var edge in _adjacentEdges[current].OrderBy(e => e.End)) 137 { 138 if (!visited[edge.End]) 139 { 140 RecursiveDFSTraversal(edge.End, visited, traversal); 141 } 142 } 143 } 144 } 145 } 146 } 147 }
參考資料
- 廣度優先搜索
- 深度優先搜索
- Breadth First Traversal for a Graph
- Depth First Traversal for a Graph
- 深度優先搜索 廣度優先搜索類訓練題
- Introduction to Algorithms 6.006 - Lecture 13
- 深度優先搜索 DFS
- 算法與數據結構
本篇文章《深度優先搜索》由 Dennis Gao 發表自博客園,未經作者本人同意禁止任何形式的轉載,任何自動或人為的爬蟲轉載行為均為耍流氓。