图-深度优先(DFS)算法简介

357 阅读3分钟

深度优先搜索(DFS)简介

深度优先搜索(DFS,Depth-First Search) 是一种用于遍历或搜索图或树的算法。DFS 从一个节点出发,沿着图的一个分支不断向下搜索,直到没有可以继续深入的节点为止。然后,它会回溯到上一个节点,继续搜索其他未访问的邻居。DFS 适合用于搜索问题、路径寻找、连通性检查等场景。

DFS 的特点是 递归(或者使用栈) 实现,并且在搜索过程中 尽可能深入 图的每一条分支,直到无法继续下去,然后回溯继续探索其他分支。

深度优先搜索的基本思路

  1. 从起始节点开始,访问该节点。
  2. 标记当前节点为已访问。
  3. 对于每个未被访问的邻居,递归地执行 DFS。
  4. 当所有邻居都被访问后,回溯到上一节点,继续探索其他未访问的邻居。

DFS 算法的实现

1. 递归实现 DFS

递归方式是 DFS 最常见的实现方法,它通过系统的调用栈自动管理回溯过程。

2. 非递归实现 DFS

通过显式使用栈数据结构来模拟递归过程,手动管理节点的访问顺序。

示例

假设我们有一个无向图,图通过邻接表(adjacency list)来表示。例如,图可以用一个对象表示,节点是键,值是与该节点相邻的节点数组。

// 定义图的结构(邻接表)
const graph = {
    0: [1, 2],
    1: [0, 3, 4],
    2: [0, 4],
    3: [1],
    4: [1, 2],
};

1. 递归实现 DFS

递归版本的 DFS 会利用栈(调用栈)来追踪路径,直到所有节点都被访问过。

代码实现

// 深度优先搜索(递归实现)
function dfsRecursive(graph, node, visited = new Set()) {
  // 标记当前节点为已访问
  visited.add(node);
  console.log(node);  // 访问当前节点
  
  // 遍历该节点的所有邻居节点
  for (let neighbor of graph[node]) {
    if (!visited.has(neighbor)) {
      // 如果邻居未被访问,递归访问
      dfsRecursive(graph, neighbor, visited);
    }
  }
}

// 示例使用
dfsRecursive(graph, 0);  // 从节点0开始DFS

输出结果

0
1
3
4
2

2. 非递归实现 DFS

非递归实现 DFS 可以通过显式使用栈来模拟递归的过程,手动管理节点的访问顺序。

代码实现

// 深度优先搜索(非递归实现)
function dfsIterative(graph, start) {
  const visited = new Set();  // 记录已访问的节点
  const stack = [start];      // 用栈来存储待访问的节点
  
  while (stack.length > 0) {
    // 从栈中弹出一个节点
    const node = stack.pop();
    
    if (!visited.has(node)) {
      visited.add(node);    // 标记该节点为已访问
      console.log(node);     // 访问当前节点
      
      // 将该节点的未访问邻居节点压入栈
      for (let neighbor of graph[node]) {
        if (!visited.has(neighbor)) {
          stack.push(neighbor);
        }
      }
    }
  }
}

// 示例使用
dfsIterative(graph, 0);  // 从节点0开始DFS

输出结果

0
2
4
1
3

比较递归和非递归实现

  • 递归实现:更简洁,利用系统的调用栈来处理回溯。
  • 非递归实现:通过手动管理栈来模拟递归,适用于栈深度较大的情况,避免递归深度过深导致栈溢出。

总结

  • DFS 是图和树的基本遍历算法,常用递归或栈实现。
  • 递归实现更简洁,但可能存在栈溢出问题,尤其是在大规模图中。
  • 非递归实现使用栈显式地模拟递归过程,适合大图遍历。
  • DFS 的应用包括图的连通性、路径查找、环检测等。

通过 DFS,你可以高效地访问和处理图结构的节点,解决许多图相关的问题。