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 最自然的写法。核心逻辑是:

  1. 标记当前节点为已访问
  2. 处理当前节点(如打印)
  3. 对当前节点的每个未访问邻居,递归调用 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 的核心步骤:

  1. 创建一个栈,将起始节点入栈
  2. 当栈不为空时:弹出一个节点,若未访问则标记为已访问并处理,然后将其所有未访问的邻居入栈

注意:为了使迭代版本的遍历顺序与递归版本一致,邻居入栈时需要按照逆序压入(因为栈是后进先出 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 + 剪枝构成回溯搜索
posted @ 2026-04-17 07:54  游翔  阅读(32)  评论(0)    收藏  举报