Breadth-first search (BFS)
Explore in rings, and get the shortest path in an unweighted graph for free.
BFS visits everything one step away, then everything two steps away, and so on. Because it expands in rings, the first time it reaches a vertex, it has reached it by the fewest possible edges. That property is why BFS answers shortest-path questions on unweighted graphs.
function bfs(graph, start) {
const visited = new Set([start]); // mark on enqueue, not on dequeue
const queue = [start];
const order = [];
while (queue.length) {
const node = queue.shift();
order.push(node);
for (const next of graph.get(node) ?? []) {
if (visited.has(next)) continue;
visited.add(next);
queue.push(next);
}
}
return order;
}
Mark on enqueue. Marking when you dequeue lets the same vertex enter the queue several times, which is the classic BFS bug — still correct, quietly quadratic.
Shortest path, with the route
Record where each vertex was reached from, then walk the chain back:
function shortestPath(graph, start, goal) {
const cameFrom = new Map([[start, null]]);
const queue = [start];
while (queue.length) {
const node = queue.shift();
if (node === goal) {
const path = [];
for (let at = goal; at !== null; at = cameFrom.get(at)) path.unshift(at);
return path;
}
for (const next of graph.get(node) ?? []) {
if (cameFrom.has(next)) continue;
cameFrom.set(next, node);
queue.push(next);
}
}
return null; // unreachable
}
Level by level
Freeze the queue length before the inner loop, exactly as in tree level-order, when you need the distance:
let distance = 0;
while (queue.length) {
const size = queue.length;
for (let i = 0; i < size; i++) { /* … */ }
distance++;
}
That shape answers "minimum number of moves", "rotting oranges", "word ladder", and every other question phrased as fewest steps.
Cost
O(V + E) time — each vertex enqueued once, each edge examined once. Space O(V) for the queue and visited set, and the queue can hold a whole level, which for a wide graph is most of the vertices.
Use shift() with care: on a large JavaScript array it is O(n). Use a head index, as in the queue chapter.