Skip to content

图的基础:存储与遍历

场景引入

社交网络中的好友关系、地图中的道路连接、代码中的模块依赖——这些都是图。图是最通用的数据结构,树和链表都是图的特例。

示例图

上图的两种存储方式:

邻接表邻接矩阵
0 → [1, 2][0,1,1,0]
1 → [0, 3][1,0,0,1]
2 → [0][1,0,0,0]
3 → [1][0,1,0,0]

DFS vs BFS 遍历流程

图的存储

邻接表(推荐)

javascript
// 用 Map<number, number[]> 表示
const graph = new Map();
graph.set(0, [1, 2]);
graph.set(1, [0, 3]);
graph.set(2, [0]);
graph.set(3, [1]);

// 或直接用数组
const graph2 = [[1,2], [0,3], [0], [1]];

空间 O(V + E),适合稀疏图。LeetCode 大多数图题用邻接表。

邻接矩阵

javascript
// matrix[i][j] = 1 表示 i 到 j 有边
const matrix = [
  [0,1,1,0],
  [1,0,0,1],
  [1,0,0,0],
  [0,1,0,0]
];

空间 O(V²),适合稠密图,查询边是否存在为 O(1)。

DFS 遍历

javascript
function dfs(graph, start) {
  const visited = new Set();

  function traverse(node) {
    if (visited.has(node)) return;
    visited.add(node);
    console.log(node); // 处理节点
    for (const neighbor of graph[node]) {
      traverse(neighbor);
    }
  }

  traverse(start);
}

BFS 遍历

javascript
function bfs(graph, start) {
  const visited = new Set([start]);
  const queue = [start];

  while (queue.length) {
    const node = queue.shift();
    console.log(node); // 处理节点
    for (const neighbor of graph[node]) {
      if (!visited.has(neighbor)) {
        visited.add(neighbor);
        queue.push(neighbor);
      }
    }
  }
}

图 vs 树的遍历区别

图可能有环,所以必须用 visited 集合防止死循环。树没有环,不需要 visited。

LC 797. 所有可能的路径

给定有向无环图(DAG),找出从 0 到 n-1 的所有路径。

javascript
function allPathsSourceTarget(graph) {
  const result = [];
  const target = graph.length - 1;

  function dfs(node, path) {
    if (node === target) {
      result.push([...path]);
      return;
    }
    for (const next of graph[node]) {
      path.push(next);
      dfs(next, path);
      path.pop();
    }
  }

  dfs(0, [0]);
  return result;
}

复杂度

算法时间空间
DFSO(V + E)O(V)
BFSO(V + E)O(V)

LeetCode 练习

  • LC 797. 所有可能的路径
  • LC 200. 岛屿数量(网格图 DFS)
  • LC 133. 克隆图

面试算法可视化图解