7-2 深度优先搜索
深度优先搜索(Depth-First Search, DFS)
深度优先搜索(Depth-First Search,简称 DFS)是一种基础的图遍历算法。它的核心思想是:从起始节点出发,沿着一条路径尽可能深入地探索,直到无法继续前进(到达死胡同或已访问节点),然后回溯(Backtrack)到上一个分支点,选择另一条路径继续探索。DFS 使用栈(Stack)作为核心数据结构——可以通过显式栈实现,也可以利用递归调用栈(Call Stack)隐式实现。
DFS 不保证最短路径,但在许多问题中有独特优势:拓扑排序(Topological Sort)、环检测(Cycle Detection)、连通分量(Connected Components)等。
本文使用如下示例图(邻接表表示):
邻接表(Adjacency List):
0: [1, 2]
1: [0, 3]
2: [0, 3]
3: [1, 2, 4, 5]
4: [3, 5]
5: [3, 4]
图的结构:
0
/ \
1 2
\ /
3
/ \
4 5
从节点 0 出发,DFS 的遍历过程如下(按邻接表顺序访问邻居):
访问 0 → 访问 0 的第一个邻居 1 → 访问 1 的第一个未访问邻居 3 → 访问 3 的第一个未访问邻居 2 → 回溯到 3 → 访问 3 的下一个未访问邻居 4 → 访问 4 的未访问邻居 5 → 回溯完成
DFS 遍历顺序: 0 1 3 2 4 5
注意:DFS 的遍历顺序取决于邻接表中邻居的存储顺序和访问顺序,不同顺序可能产生不同的遍历结果,但所有节点最终都会被访问到。
图的表示
本文使用与 BFS 相同的邻接表(Adjacency List)表示法。不同语言的实现方式:C++ 使用 vector<vector<int>>,C 语言使用二维数组模拟,Python 使用字典嵌套列表,Go 使用 [][]int 切片。
#include <iostream>
#include <vector>
using namespace std;
int main() {
// Build adjacency list for the example graph
// 6 nodes: 0-5
int n = 6;
vector<vector<int>> adj(n);
// Add edges (undirected graph, add both directions)
adj[0].push_back(1);
adj[0].push_back(2);
adj[1].push_back(0);
adj[1].push_back(3);
adj[2].push_back(0);
adj[2].push_back(3);
adj[3].push_back(1);
adj[3].push_back(2);
adj[3].push_back(4);
adj[3].push_back(5);
adj[4].push_back(3);
adj[4].push_back(5);
adj[5].push_back(3);
adj[5].push_back(4);
// Print adjacency list
for (int i = 0; i < n; i++) {
cout << i << ": ";
for (int neighbor : adj[i]) {
cout << neighbor << " ";
}
cout << endl;
}
return 0;
}
#include <stdio.h>
#define MAX_NODES 6
#define MAX_NEIGHBORS 4
int main() {
// Use 2D array to represent adjacency list
int adj[MAX_NODES][MAX_NEIGHBORS] = {0};
int adjCount[MAX_NODES] = {0};
#define ADD_EDGE(u, v) do { \
adj[u][adjCount[u]++] = v; \
adj[v][adjCount[v]++] = u; \
} while(0)
// Build the example graph
ADD_EDGE(0, 1);
ADD_EDGE(0, 2);
ADD_EDGE(1, 3);
ADD_EDGE(2, 3);
ADD_EDGE(3, 4);
ADD_EDGE(3, 5);
ADD_EDGE(4, 5);
// Print adjacency list
for (int i = 0; i < MAX_NODES; i++) {
printf("%d: ", i);
for (int j = 0; j < adjCount[i]; j++) {
printf("%d ", adj[i][j]);
}
printf("\n");
}
return 0;
}
def main():
# Build adjacency list using dictionary
adj = {
0: [1, 2],
1: [0, 3],
2: [0, 3],
3: [1, 2, 4, 5],
4: [3, 5],
5: [3, 4],
}
# Print adjacency list
for node in sorted(adj.keys()):
print(f"{node}: {adj[node]}")
if __name__ == "__main__":
main()
package main
import "fmt"
func main() {
// Build adjacency list using slice of slices
adj := [][]int{
{1, 2}, // node 0
{0, 3}, // node 1
{0, 3}, // node 2
{1, 2, 4, 5}, // node 3
{3, 5}, // node 4
{3, 4}, // node 5
}
// Print adjacency list
for i, neighbors := range adj {
fmt.Printf("%d: %v\n", i, neighbors)
}
}
上述代码构建了示例图的邻接表表示。C++ 使用 vector<vector<int>> 动态管理邻接关系;C 语言用固定大小的二维数组配合计数数组存储;Python 使用字典嵌套列表,最为简洁;Go 使用 [][]int 切片直接初始化。
运行该程序将输出:
0: 1 2
1: 0 3
2: 0 3
3: 1 2 4 5
4: 3 5
5: 3 4
DFS 递归实现
递归(Recursive)实现是 DFS 最自然的写法。核心逻辑是:
- 标记当前节点为已访问
- 处理当前节点(如打印)
- 对当前节点的每个未访问邻居,递归调用 DFS
递归调用栈本身就充当了栈(Stack)的角色——每深入一层就压入一个栈帧(Stack Frame),回溯时自动弹出。
递归过程跟踪(从节点 0 出发):
dfs(0): 标记0, 打印0
邻居1未访问 → dfs(1): 标记1, 打印1
邻居0已访问, 邻居3未访问 → dfs(3): 标记3, 打印3
邻居1已访问, 邻居2未访问 → dfs(2): 标记2, 打印2
邻居0已访问, 邻居3已访问 → 返回
邻居4未访问 → dfs(4): 标记4, 打印4
邻居3已访问, 邻居5未访问 → dfs(5): 标记5, 打印5
邻居3已访问, 邻居4已访问 → 返回
返回
返回
返回
邻居2已访问 → 返回
遍历结果: 0 1 3 2 4 5
C++ 实现
#include <iostream>
#include <vector>
using namespace std;
// Recursive DFS helper function
void dfsRecursive(const vector<vector<int>>& adj, int node, vector<bool>& visited) {
// Mark current node as visited and process it
visited[node] = true;
cout << node << " ";
// Recurse on all unvisited neighbors
for (int neighbor : adj[node]) {
if (!visited[neighbor]) {
dfsRecursive(adj, neighbor, visited);
}
}
}
// Wrapper function for DFS from a starting node
void dfs(const vector<vector<int>>& adj, int start) {
int n = adj.size();
vector<bool> visited(n, false);
dfsRecursive(adj, start, visited);
}
int main() {
vector<vector<int>> adj = {
{1, 2}, // node 0
{0, 3}, // node 1
{0, 3}, // node 2
{1, 2, 4, 5}, // node 3
{3, 5}, // node 4
{3, 4}, // node 5
};
cout << "DFS (recursive) from node 0: ";
dfs(adj, 0);
cout << endl;
return 0;
}
C 实现
#include <stdio.h>
#include <stdbool.h>
#define MAX_NODES 6
#define MAX_NEIGHBORS 4
// Recursive DFS helper function
void dfsRecursive(int adj[][MAX_NEIGHBORS], int adjCount[], int node, bool visited[]) {
// Mark current node as visited and process it
visited[node] = true;
printf("%d ", node);
// Recurse on all unvisited neighbors
for (int i = 0; i < adjCount[node]; i++) {
int neighbor = adj[node][i];
if (!visited[neighbor]) {
dfsRecursive(adj, adjCount, neighbor, visited);
}
}
}
// Wrapper function for DFS from a starting node
void dfs(int adj[][MAX_NEIGHBORS], int adjCount[], int start, int n) {
bool visited[MAX_NODES] = {false};
dfsRecursive(adj, adjCount, start, visited);
}
int main() {
int adj[MAX_NODES][MAX_NEIGHBORS] = {0};
int adjCount[MAX_NODES] = {0};
#define ADD_EDGE(u, v) do { \
adj[u][adjCount[u]++] = v; \
adj[v][adjCount[v]++] = u; \
} while(0)
ADD_EDGE(0, 1);
ADD_EDGE(0, 2);
ADD_EDGE(1, 3);
ADD_EDGE(2, 3);
ADD_EDGE(3, 4);
ADD_EDGE(3, 5);
ADD_EDGE(4, 5);
printf("DFS (recursive) from node 0: ");
dfs(adj, adjCount, 0, MAX_NODES);
printf("\n");
return 0;
}
Python 实现
def dfs_recursive(adj, node, visited):
"""Recursive DFS helper function."""
# Mark current node as visited and process it
visited[node] = True
print(node, end=" ")
# Recurse on all unvisited neighbors
for neighbor in adj[node]:
if not visited[neighbor]:
dfs_recursive(adj, neighbor, visited)
def dfs(adj, start):
"""Wrapper function for DFS from a starting node."""
n = len(adj)
visited = [False] * n
dfs_recursive(adj, start, visited)
if __name__ == "__main__":
adj = {
0: [1, 2],
1: [0, 3],
2: [0, 3],
3: [1, 2, 4, 5],
4: [3, 5],
5: [3, 4],
}
print("DFS (recursive) from node 0:", end=" ")
dfs(adj, 0)
print()
Go 实现
package main
import "fmt"
// Recursive DFS helper function
func dfsRecursive(adj [][]int, node int, visited []bool) {
// Mark current node as visited and process it
visited[node] = true
fmt.Print(node, " ")
// Recurse on all unvisited neighbors
for _, neighbor := range adj[node] {
if !visited[neighbor] {
dfsRecursive(adj, neighbor, visited)
}
}
}
// Wrapper function for DFS from a starting node
func dfs(adj [][]int, start int) {
n := len(adj)
visited := make([]bool, n)
dfsRecursive(adj, start, visited)
}
func main() {
adj := [][]int{
{1, 2}, // node 0
{0, 3}, // node 1
{0, 3}, // node 2
{1, 2, 4, 5}, // node 3
{3, 5}, // node 4
{3, 4}, // node 5
}
fmt.Print("DFS (recursive) from node 0: ")
dfs(adj, 0)
fmt.Println()
}
上述代码使用递归方式实现 DFS。递归函数 dfsRecursive 的逻辑非常清晰:先标记当前节点为已访问并处理它,然后对所有未访问的邻居递归调用自身。递归的深度取决于图中最长路径的长度,在最坏情况下(图为一条链)递归深度可达 O(V)。
运行该程序将输出:
DFS (recursive) from node 0: 0 1 3 2 4 5
DFS 迭代实现(使用栈)
递归实现虽然简洁,但在深度很大的图上可能导致栈溢出(Stack Overflow)。迭代(Iterative)实现使用显式的栈(Stack)数据结构来替代递归调用栈,避免了栈溢出风险。
迭代 DFS 的核心步骤:
- 创建一个栈,将起始节点入栈
- 当栈不为空时:弹出一个节点,若未访问则标记为已访问并处理,然后将其所有未访问的邻居入栈
注意:为了使迭代版本的遍历顺序与递归版本一致,邻居入栈时需要按照逆序压入(因为栈是后进先出 LIFO)。
C++ 实现
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
// Iterative DFS using explicit stack
void dfsIterative(const vector<vector<int>>& adj, int start) {
int n = adj.size();
vector<bool> visited(n, false);
stack<int> stk;
stk.push(start);
while (!stk.empty()) {
int node = stk.top();
stk.pop();
// Skip if already visited
if (visited[node]) continue;
// Mark as visited and process
visited[node] = true;
cout << node << " ";
// Push neighbors in reverse order to match recursive DFS order
for (int i = adj[node].size() - 1; i >= 0; i--) {
int neighbor = adj[node][i];
if (!visited[neighbor]) {
stk.push(neighbor);
}
}
}
}
int main() {
vector<vector<int>> adj = {
{1, 2}, // node 0
{0, 3}, // node 1
{0, 3}, // node 2
{1, 2, 4, 5}, // node 3
{3, 5}, // node 4
{3, 4}, // node 5
};
cout << "DFS (iterative) from node 0: ";
dfsIterative(adj, 0);
cout << endl;
return 0;
}
C 实现
#include <stdio.h>
#include <stdbool.h>
#define MAX_NODES 6
#define MAX_NEIGHBORS 4
// Simple stack implementation
typedef struct {
int data[MAX_NODES];
int top;
} Stack;
void stackInit(Stack* s) { s->top = -1; }
bool stackEmpty(Stack* s) { return s->top == -1; }
void stackPush(Stack* s, int val) { s->data[++s->top] = val; }
int stackPop(Stack* s) { return s->data[s->top--]; }
// Iterative DFS using explicit stack
void dfsIterative(int adj[][MAX_NEIGHBORS], int adjCount[], int start, int n) {
bool visited[MAX_NODES] = {false};
Stack stk;
stackInit(&stk);
stackPush(&stk, start);
while (!stackEmpty(&stk)) {
int node = stackPop(&stk);
// Skip if already visited
if (visited[node]) continue;
// Mark as visited and process
visited[node] = true;
printf("%d ", node);
// Push neighbors in reverse order to match recursive DFS order
for (int i = adjCount[node] - 1; i >= 0; i--) {
int neighbor = adj[node][i];
if (!visited[neighbor]) {
stackPush(&stk, neighbor);
}
}
}
}
int main() {
int adj[MAX_NODES][MAX_NEIGHBORS] = {0};
int adjCount[MAX_NODES] = {0};
#define ADD_EDGE(u, v) do { \
adj[u][adjCount[u]++] = v; \
adj[v][adjCount[v]++] = u; \
} while(0)
ADD_EDGE(0, 1);
ADD_EDGE(0, 2);
ADD_EDGE(1, 3);
ADD_EDGE(2, 3);
ADD_EDGE(3, 4);
ADD_EDGE(3, 5);
ADD_EDGE(4, 5);
printf("DFS (iterative) from node 0: ");
dfsIterative(adj, adjCount, 0, MAX_NODES);
printf("\n");
return 0;
}
Python 实现
def dfs_iterative(adj, start):
"""Iterative DFS using explicit stack."""
n = len(adj)
visited = [False] * n
stack = [start]
while stack:
node = stack.pop()
# Skip if already visited
if visited[node]:
continue
# Mark as visited and process
visited[node] = True
print(node, end=" ")
# Push neighbors in reverse order to match recursive DFS order
for neighbor in reversed(adj[node]):
if not visited[neighbor]:
stack.append(neighbor)
if __name__ == "__main__":
adj = {
0: [1, 2],
1: [0, 3],
2: [0, 3],
3: [1, 2, 4, 5],
4: [3, 5],
5: [3, 4],
}
print("DFS (iterative) from node 0:", end=" ")
dfs_iterative(adj, 0)
print()
Go 实现
package main
import "fmt"
// Iterative DFS using explicit stack
func dfsIterative(adj [][]int, start int) {
n := len(adj)
visited := make([]bool, n)
// Use slice as a simple stack
stack := []int{start}
for len(stack) > 0 {
// Pop from top
node := stack[len(stack)-1]
stack = stack[:len(stack)-1]
// Skip if already visited
if visited[node] {
continue
}
// Mark as visited and process
visited[node] = true
fmt.Print(node, " ")
// Push neighbors in reverse order to match recursive DFS order
neighbors := adj[node]
for i := len(neighbors) - 1; i >= 0; i-- {
if !visited[neighbors[i]] {
stack = append(stack, neighbors[i])
}
}
}
}
func main() {
adj := [][]int{
{1, 2}, // node 0
{0, 3}, // node 1
{0, 3}, // node 2
{1, 2, 4, 5}, // node 3
{3, 5}, // node 4
{3, 4}, // node 5
}
fmt.Print("DFS (iterative) from node 0: ")
dfsIterative(adj, 0)
fmt.Println()
}
上述代码使用显式栈实现 DFS。关键细节是邻居入栈时使用逆序——因为栈是后进先出(LIFO),先入栈的会后弹出。逆序入栈使得邻接表中靠前的邻居被优先访问,从而与递归版本的遍历顺序保持一致。C++ 使用 STL 的 stack;C 语言手动实现基于数组的栈;Python 直接使用列表的 append() 和 pop();Go 使用切片模拟栈操作。
运行该程序将输出:
DFS (iterative) from node 0: 0 1 3 2 4 5
DFS 检测环
环检测(Cycle Detection)是 DFS 的重要应用之一。核心思路是使用三色标记法(Three-Color Marking):
- WHITE(白色):节点尚未被访问
- GRAY(灰色):节点已被访问但尚未完成所有邻居的探索(正在当前递归路径上)
- BLACK(黑色):节点的所有邻居都已探索完毕
如果在 DFS 过程中遇到一个 GRAY 节点,说明当前路径回指了路径上的某个节点——这就是一个环(Cycle)。因为 GRAY 节点代表"正在探索中",遇到 GRAY 意味着存在一条从该节点出发又回到自身的路径。
示例图(有环):
0 → 1 → 2 → 3 → 1 (1→2→3→1 构成环)
DFS 检测过程:
dfs(0): 标记0为GRAY
邻居1(WHITE) → dfs(1): 标记1为GRAY
邻居2(WHITE) → dfs(2): 标记2为GRAY
邻居3(WHITE) → dfs(3): 标记3为GRAY
邻居1(GRAY!) → 检测到环! 1→2→3→1
C++ 实现
#include <iostream>
#include <vector>
using namespace std;
// Color constants for cycle detection
const int WHITE = 0; // Unvisited
const int GRAY = 1; // In current DFS path (being explored)
const int BLACK = 2; // Fully explored
// Returns true if a cycle is found
bool hasCycleDFS(const vector<vector<int>>& adj, int node, vector<int>& color) {
color[node] = GRAY; // Mark as being explored
for (int neighbor : adj[node]) {
if (color[neighbor] == GRAY) {
// Found a back edge → cycle detected
return true;
}
if (color[neighbor] == WHITE) {
if (hasCycleDFS(adj, neighbor, color)) {
return true;
}
}
}
color[node] = BLACK; // Mark as fully explored
return false;
}
bool hasCycle(const vector<vector<int>>& adj) {
int n = adj.size();
vector<int> color(n, WHITE);
for (int i = 0; i < n; i++) {
if (color[i] == WHITE) {
if (hasCycleDFS(adj, i, color)) {
return true;
}
}
}
return false;
}
int main() {
// Graph with cycle: 0-1-2-3-4-1 (cycle: 1-2-3-4-1)
vector<vector<int>> adjWithCycle = {
{1}, // node 0
{0, 2, 4}, // node 1
{1, 3}, // node 2
{2, 4}, // node 3
{1, 3}, // node 4
};
cout << "Graph with cycle: "
<< (hasCycle(adjWithCycle) ? "cycle detected" : "no cycle") << endl;
// Graph without cycle (tree): 0-1, 0-2, 1-3, 1-4
vector<vector<int>> adjNoCycle = {
{1, 2}, // node 0
{0, 3, 4}, // node 1
{0}, // node 2
{1}, // node 3
{1}, // node 4
};
cout << "Graph without cycle: "
<< (hasCycle(adjNoCycle) ? "cycle detected" : "no cycle") << endl;
return 0;
}
C 实现
#include <stdio.h>
#include <stdbool.h>
#define MAX_NODES 6
#define MAX_NEIGHBORS 4
#define WHITE 0
#define GRAY 1
#define BLACK 2
bool hasCycleDFS(int adj[][MAX_NEIGHBORS], int adjCount[], int node, int color[]) {
color[node] = GRAY; // Mark as being explored
for (int i = 0; i < adjCount[node]; i++) {
int neighbor = adj[node][i];
if (color[neighbor] == GRAY) {
// Found a back edge → cycle detected
return true;
}
if (color[neighbor] == WHITE) {
if (hasCycleDFS(adj, adjCount, neighbor, color)) {
return true;
}
}
}
color[node] = BLACK; // Mark as fully explored
return false;
}
bool hasCycle(int adj[][MAX_NEIGHBORS], int adjCount[], int n) {
int color[MAX_NODES] = {0}; // All WHITE initially
for (int i = 0; i < n; i++) {
if (color[i] == WHITE) {
if (hasCycleDFS(adj, adjCount, i, color)) {
return true;
}
}
}
return false;
}
int main() {
// Graph with cycle: 0-1-2-3-4-1 (cycle: 1-2-3-4-1)
int adjWithCycle[MAX_NODES][MAX_NEIGHBORS] = {0};
int adjCountWithCycle[MAX_NODES] = {0};
#define ADD_EDGE_WC(u, v) do { \
adjWithCycle[u][adjCountWithCycle[u]++] = v; \
adjWithCycle[v][adjCountWithCycle[v]++] = u; \
} while(0)
ADD_EDGE_WC(0, 1);
ADD_EDGE_WC(1, 2);
ADD_EDGE_WC(2, 3);
ADD_EDGE_WC(3, 4);
ADD_EDGE_WC(4, 1); // This edge creates a cycle: 1-2-3-4-1
printf("Graph with cycle: %s\n",
hasCycle(adjWithCycle, adjCountWithCycle, 5) ? "cycle detected" : "no cycle");
// Graph without cycle (tree): 0-1, 0-2, 1-3, 1-4
int adjNoCycle[MAX_NODES][MAX_NEIGHBORS] = {0};
int adjCountNoCycle[MAX_NODES] = {0};
#define ADD_EDGE_NC(u, v) do { \
adjNoCycle[u][adjCountNoCycle[u]++] = v; \
adjNoCycle[v][adjCountNoCycle[v]++] = u; \
} while(0)
ADD_EDGE_NC(0, 1);
ADD_EDGE_NC(0, 2);
ADD_EDGE_NC(1, 3);
ADD_EDGE_NC(1, 4);
printf("Graph without cycle: %s\n",
hasCycle(adjNoCycle, adjCountNoCycle, 5) ? "cycle detected" : "no cycle");
return 0;
}
Python 实现
WHITE, GRAY, BLACK = 0, 1, 2
def has_cycle_dfs(adj, node, color):
"""DFS helper for cycle detection using three-color marking."""
color[node] = GRAY # Mark as being explored
for neighbor in adj[node]:
if color[neighbor] == GRAY:
# Found a back edge → cycle detected
return True
if color[neighbor] == WHITE:
if has_cycle_dfs(adj, neighbor, color):
return True
color[node] = BLACK # Mark as fully explored
return False
def has_cycle(adj):
"""Check if an undirected graph contains a cycle."""
n = len(adj)
color = [WHITE] * n
for i in range(n):
if color[i] == WHITE:
if has_cycle_dfs(adj, i, color):
return True
return False
if __name__ == "__main__":
# Graph with cycle: 0-1-2-3-4-1 (cycle: 1-2-3-4-1)
adj_with_cycle = {
0: [1],
1: [0, 2, 4],
2: [1, 3],
3: [2, 4],
4: [1, 3],
}
print("Graph with cycle:",
"cycle detected" if has_cycle(adj_with_cycle) else "no cycle")
# Graph without cycle (tree): 0-1, 0-2, 1-3, 1-4
adj_no_cycle = {
0: [1, 2],
1: [0, 3, 4],
2: [0],
3: [1],
4: [1],
}
print("Graph without cycle:",
"cycle detected" if has_cycle(adj_no_cycle) else "no cycle")
Go 实现
package main
import "fmt"
const (
White = 0 // Unvisited
Gray = 1 // In current DFS path
Black = 2 // Fully explored
)
// DFS helper for cycle detection
func hasCycleDFS(adj [][]int, node int, color []int) bool {
color[node] = Gray // Mark as being explored
for _, neighbor := range adj[node] {
if color[neighbor] == Gray {
// Found a back edge → cycle detected
return true
}
if color[neighbor] == White {
if hasCycleDFS(adj, neighbor, color) {
return true
}
}
}
color[node] = Black // Mark as fully explored
return false
}
// Check if the graph contains a cycle
func hasCycle(adj [][]int) bool {
n := len(adj)
color := make([]int, n) // All White (0) initially
for i := 0; i < n; i++ {
if color[i] == White {
if hasCycleDFS(adj, i, color) {
return true
}
}
}
return false
}
func main() {
// Graph with cycle: 0-1-2-3-4-1 (cycle: 1-2-3-4-1)
adjWithCycle := [][]int{
{1}, // node 0
{0, 2, 4}, // node 1
{1, 3}, // node 2
{2, 4}, // node 3
{1, 3}, // node 4
}
result := "no cycle"
if hasCycle(adjWithCycle) {
result = "cycle detected"
}
fmt.Println("Graph with cycle:", result)
// Graph without cycle (tree): 0-1, 0-2, 1-3, 1-4
adjNoCycle := [][]int{
{1, 2}, // node 0
{0, 3, 4}, // node 1
{0}, // node 2
{1}, // node 3
{1}, // node 4
}
result = "no cycle"
if hasCycle(adjNoCycle) {
result = "cycle detected"
}
fmt.Println("Graph without cycle:", result)
}
上述代码使用三色标记法检测无向图中的环。hasCycleDFS 函数在访问节点时将其标记为 GRAY(正在探索),递归处理所有邻居。如果在递归过程中遇到一个 GRAY 节点,说明当前搜索路径回指了自身,存在环。处理完所有邻居后,将节点标记为 BLACK(已完成)。外层 hasCycle 函数对所有未访问的节点启动 DFS,以处理非连通图的情况。
测试使用了两个图:第一个图包含环 1-2-3-4-1;第二个图是一棵树(Tree),不包含任何环。
运行该程序将输出:
Graph with cycle: cycle detected
Graph without cycle: no cycle
DFS 的性质
下表总结了 DFS 算法的时间和空间复杂度:
| 指标 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度(Time Complexity) | O(V + E) | 每个顶点最多访问一次(O(V)),每条边最多检查两次(O(E)) |
| 空间复杂度(Space Complexity) | O(V) | 递归栈深度或显式栈最大为 V,visited 数组大小为 V |
其中 V 是顶点数(Vertex Count),E 是边数(Edge Count)。
DFS 的关键性质:
- 深度优先:DFS 优先探索深层节点,适合需要"走到尽头再回溯"的场景。
- 不保证最短路径:DFS 找到的路径不一定是最短路径。需要最短路径时应使用 BFS 或 Dijkstra 算法。
- 递归与迭代等价:递归实现和栈迭代实现在逻辑上完全等价,只是栈的管理方式不同。
- 生成树:DFS 遍历的路径构成一棵 DFS 生成树(DFS Spanning Tree),树中的边称为树边(Tree Edge),其余边称为回边(Back Edge)。
DFS 的典型应用场景:
| 应用场景 | 说明 |
|---|---|
| 拓扑排序(Topological Sort) | 对有向无环图(DAG)进行线性排序,DFS 完成时的逆序即为拓扑序 |
| 环检测(Cycle Detection) | 使用三色标记法或仅 visited + parent 判断有向/无向图中的环 |
| 连通分量(Connected Components) | 对无向图找所有连通分量,对有向图找强连通分量(Kosaraju / Tarjan 算法) |
| 迷宫求解(Maze Solving) | 探索所有可能的路径,找到一条从入口到出口的路径 |
| 割点与桥(Articulation Points & Bridges) | 找出删除后会使图不连通的关键节点和边 |
| 二叉树遍历 | 前序、中序、后序遍历本质上都是 DFS |
| 回溯算法(Backtracking) | 八皇后、数独等问题中,DFS + 剪枝构成回溯搜索 |

浙公网安备 33010602011771号