Docs
/
DSA Advanced
Chapter 4

04 — Graph BFS & DFS

Representations

// Adjacency List (preferred — sparse graphs)
const adj: number[][] = Array.from({ length: n }, () => []);
for (const [u, v] of edges) {
  adj[u].push(v);
  adj[v].push(u); // Undirected
}

// Adjacency Matrix (dense graphs)
const matrix: boolean[][] = Array.from({ length: n }, () => new Array(n).fill(false));

BFS (Breadth-First Search)

Level-by-level traversal. Shortest path in unweighted graphs.

function bfs(adj: number[][], start: number, n: number): number[] {
  const dist = new Array(n).fill(-1);
  dist[start] = 0;
  const queue = [start];
  
  while (queue.length > 0) {
    const u = queue.shift()!;
    for (const v of adj[u]) {
      if (dist[v] === -1) {
        dist[v] = dist[u] + 1;
        queue.push(v);
      }
    }
  }
  return dist;
}

DFS (Depth-First Search)

Go as deep as possible, then backtrack.

function dfs(adj: number[][], start: number, n: number): boolean[] {
  const visited = new Array(n).fill(false);
  
  function explore(u: number) {
    visited[u] = true;
    for (const v of adj[u]) {
      if (!visited[v]) explore(v);
    }
  }
  
  explore(start);
  return visited;
}

Connected Components

function countComponents(n: number, adj: number[][]): number {
  const visited = new Array(n).fill(false);
  let count = 0;
  
  for (let i = 0; i < n; i++) {
    if (!visited[i]) {
      count++;
      // BFS or DFS from i
      const queue = [i];
      visited[i] = true;
      while (queue.length) {
        const u = queue.shift()!;
        for (const v of adj[u]) {
          if (!visited[v]) {
            visited[v] = true;
            queue.push(v);
          }
        }
      }
    }
  }
  return count;
}

Cycle Detection

Undirected Graph (DFS)

function hasCycleUndirected(adj: number[][], n: number): boolean {
  const visited = new Array(n).fill(false);
  
  function dfs(u: number, parent: number): boolean {
    visited[u] = true;
    for (const v of adj[u]) {
      if (!visited[v]) {
        if (dfs(v, u)) return true;
      } else if (v !== parent) {
        return true; // Back edge → cycle
      }
    }
    return false;
  }
  
  for (let i = 0; i < n; i++) {
    if (!visited[i] && dfs(i, -1)) return true;
  }
  return false;
}

Directed Graph (DFS — 3 colors)

function hasCycleDirected(adj: number[][], n: number): boolean {
  const color = new Array(n).fill(0); // 0=white, 1=gray, 2=black
  
  function dfs(u: number): boolean {
    color[u] = 1; // Gray (in progress)
    for (const v of adj[u]) {
      if (color[v] === 1) return true;  // Back edge → cycle
      if (color[v] === 0 && dfs(v)) return true;
    }
    color[u] = 2; // Black (done)
    return false;
  }
  
  for (let i = 0; i < n; i++) {
    if (color[i] === 0 && dfs(i)) return true;
  }
  return false;
}

Topological Sort

Linear ordering of DAG vertices. Every edge u → v means u comes before v.

// Kahn's Algorithm (BFS-based)
function topSort(adj: number[][], n: number): number[] {
  const inDegree = new Array(n).fill(0);
  for (let u = 0; u < n; u++) {
    for (const v of adj[u]) inDegree[v]++;
  }
  
  const queue = [];
  for (let i = 0; i < n; i++) {
    if (inDegree[i] === 0) queue.push(i);
  }
  
  const order: number[] = [];
  while (queue.length) {
    const u = queue.shift()!;
    order.push(u);
    for (const v of adj[u]) {
      inDegree[v]--;
      if (inDegree[v] === 0) queue.push(v);
    }
  }
  
  return order.length === n ? order : []; // Empty if cycle exists
}

Bipartite Check (2-Coloring)

function isBipartite(adj: number[][], n: number): boolean {
  const color = new Array(n).fill(-1);
  
  for (let i = 0; i < n; i++) {
    if (color[i] !== -1) continue;
    color[i] = 0;
    const queue = [i];
    
    while (queue.length) {
      const u = queue.shift()!;
      for (const v of adj[u]) {
        if (color[v] === -1) {
          color[v] = 1 - color[u];
          queue.push(v);
        } else if (color[v] === color[u]) {
          return false; // Same color → not bipartite
        }
      }
    }
  }
  return true;
}

Grid BFS (Shortest Path in Matrix)

function shortestPath(grid: number[][], start: [number, number], end: [number, number]): number {
  const [rows, cols] = [grid.length, grid[0].length];
  const dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]];
  const visited = Array.from({ length: rows }, () => new Array(cols).fill(false));
  
  const queue: [number, number, number][] = [[start[0], start[1], 0]];
  visited[start[0]][start[1]] = true;
  
  while (queue.length) {
    const [r, c, dist] = queue.shift()!;
    if (r === end[0] && c === end[1]) return dist;
    
    for (const [dr, dc] of dirs) {
      const nr = r + dr, nc = c + dc;
      if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && !visited[nr][nc] && grid[nr][nc] === 0) {
        visited[nr][nc] = true;
        queue.push([nr, nc, dist + 1]);
      }
    }
  }
  return -1;
}

Key Takeaways

  • BFS = shortest path (unweighted), level-order; DFS = cycle detection, topological sort
  • Cycle detection: undirected = parent check; directed = 3-color (white/gray/black)
  • Topological sort (Kahn's) = BFS with in-degree; also detects cycles in DAGs
  • Bipartite = 2-colorable = no odd-length cycles
  • Grid problems are just graph problems — use BFS with 4-directional neighbors