Connected Components Count
Counting components is the same stack DFS as `hasPath`, run from every node in the graph, with one twist: `exploreGraph` returns 0 or 1 instead of a node list, so the total becomes a running sum. What keeps a component from being counted more than once is a single `visited` set shared across every call, not any per-component bookkeeping.
The problem
Implement `connectedComponentsCount(graph)` where `graph` is an adjacency list (`{ node: [neighbours] }`). Return how many separate connected components it has — the two-cluster graph (`0`/`1`/`5`/`8` linked together, `2`/`3`/`4` linked together) should report `2`, and a graph mixing two isolated nodes with one connected pair (`a: [], b: [], c: ['d'], d: ['c']`) should report `3`.
Unlike `hasPath`/`undirectedPath`, this isn't a yes/no about two specific nodes — it has to visit every node in the graph via `for...in` and, for each one, decide whether it already belongs to a component that's been counted or is the start of a new one.
The approach
The `for...in` loop tries every key in `graph` as a potential DFS start, but `visited` is declared once outside the loop and threaded into every `exploreGraph` call as an argument — that's the entire trick. The first node of a component to reach `exploreGraph` runs the full stack-based traversal and marks every reachable node visited; every later node from that same component hits `if (visited.has(source)) return 0` immediately and contributes nothing further.
`exploreGraph` reuses the exact iterative stack DFS from `hasPath`/`dfs` — push a starting node, pop, walk its neighbours, push whichever aren't visited yet — but repurposes the return value: `1` means 'this call just discovered a new component', `0` means 'this node was already accounted for'. Summing those across the outer loop is the whole algorithm — counting by side effect instead of collecting an explicit list of components.
Isolated nodes fall out for free: `a: []` still gets pushed as `source`, the inner `for` over `graph[node]` simply never runs, and the function still returns `1` — a component of size one is still a component.
The solution
export function connectedComponentsCount(graph) {
const visited = new Set()
let noOfComponents = 0
for (let node in graph) {
noOfComponents += exploreGraph(graph, node, visited)
}
return noOfComponents
}
export function exploreGraph(graph, source, visited) {
if (visited.has(source)) return 0
visited.add(source)
const stack = [source]
while (stack.length) {
const node = stack.pop()
for (let neighbour of graph[node]) {
if (!visited.has(neighbour)) {
visited.add(neighbour)
stack.push(neighbour)
}
}
}
return 1
}Time O(V + E)Space O(V)