Breadth-first order of a graph
A graph has n nodes numbered 0 to n − 1 and a list of undirected
edges. Traverse it breadth-first and return the nodes in the order they are taken from the queue:
- Start with node 0 in the queue.
- Repeatedly take the node at the front. Add each of its unvisited neighbours to the back of the queue, in increasing order of number.
- When the queue is empty and some node is unvisited, put the smallest-numbered such node in the queue and continue.
Examples
| Input | Output | Why |
|---|---|---|
n = 7, edges = [[0,1],[0,2],[1,3],[2,3],[3,4],[5,6]] | [0, 1, 2, 3, 4, 5, 6] | 1 and 2 are one step from 0, 3 is two steps, 4 is three. Then the second part. |
n = 5, edges = [[0,4],[4,3],[3,2],[2,1]] | [0, 4, 3, 2, 1] | A path: one node per level. |
n = 4, edges = [] | [0, 1, 2, 3] | Four separate nodes. |
Constraints
- 1 ≤ n ≤ 10⁴
- 0 ≤ edges.length ≤ 2 · 10⁴
- No self-loops and no repeated edges
Hints
Hint 1
Use a queue (a deque, or an array with a moving head index), not a list you remove from the front of.
Hint 2
Mark a node visited at the moment you add it to the queue. Then it can never be added twice.
Hint 3
Wrap the whole search in a loop over every node, starting a new search from each one still unvisited.
Where people lose marks
- Using list.pop(0) in Python or array.shift() in JavaScript: each is O(n), and the traversal becomes quadratic.
- Marking on dequeue, so the same node is queued many times on a dense graph.
- Forgetting the outer loop, which leaves every other part of a disconnected graph unvisited.
Dry run on 7,[[0,1],[0,2],[1,3],[2,3],[3,4],[5,6]]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 9queue (front → back)00visit order—0
Mark 0 visited and put it in the queue.
-
Step 2 / 9queue (front → back)1021visit order00
Take 0 from the front of the queue. Its unvisited neighbours 1, 2 are marked now and joined the back of the queue.
-
Step 3 / 9queue (front → back)2031visit order0011
Take 1 from the front of the queue. Its unvisited neighbours 3 are marked now and joined the back of the queue.
-
Step 4 / 9queue (front → back)30visit order001122
Take 2 from the front of the queue. It has no unvisited neighbours.
-
Step 5 / 9queue (front → back)40visit order00112233
Take 3 from the front of the queue. Its unvisited neighbours 4 are marked now and joined the back of the queue.
-
Step 6 / 9queue (front → back)empty0visit order0011223344
Take 4 from the front of the queue. It has no unvisited neighbours.
-
Step 7 / 9queue (front → back)50visit order0011223344
The queue is empty but 5 is unvisited: start again from 5.
-
Step 8 / 9queue (front → back)60visit order001122334455
Take 5 from the front of the queue. Its unvisited neighbours 6 are marked now and joined the back of the queue.
-
Step 9 / 9queue (front → back)empty0visit order00112233445566
Take 6 from the front of the queue. It has no unvisited neighbours. The queue is empty and every node is visited. Order: 0, 1, 2, 3, 4, 5, 6.
How to think about it
Breadth-first traversal visits nodes in order of their distance from the start: first the start, then every node one edge away, then every node two edges away. A first-in, first-out queue produces exactly that order, because every node at distance d joins the queue before any node at distance d + 1.
- Sort each adjacency list so neighbours join the queue smallest first.
- Mark a neighbour as visited when it is enqueued. If you wait until it is dequeued, a node with several visited neighbours enters the queue several times.
- An outer loop restarts the search on each part of a disconnected graph.
Complexity. Time O(n + m log m) · Space O(n + m)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
from collections import deque
def bfs_order(n, edges):
adj = [[] for _ in range(n)]
for u, v in edges:
adj[u].append(v)
adj[v].append(u)
for nb in adj:
nb.sort()
seen = [False] * n
order = []
for s in range(n):
if seen[s]:
continue
seen[s] = True
q = deque([s])
while q:
u = q.popleft()
order.append(u)
for v in adj[u]:
if not seen[v]:
seen[v] = True # mark on enqueue, so v is queued once
q.append(v)
return order#include <algorithm>
#include <queue>
#include <vector>
std::vector<int> solve(int n, const std::vector<std::vector<int>>& edges) {
std::vector<std::vector<int>> adj(n);
for (const auto& e : edges) {
adj[e[0]].push_back(e[1]);
adj[e[1]].push_back(e[0]);
}
for (auto& nb : adj) std::sort(nb.begin(), nb.end());
std::vector<bool> seen(n, false);
std::vector<int> order;
for (int s = 0; s < n; ++s) {
if (seen[s]) continue;
seen[s] = true;
std::queue<int> q;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
order.push_back(u);
for (int v : adj[u])
if (!seen[v]) { seen[v] = true; q.push(v); } // mark on enqueue
}
}
return order;
}import java.util.*;
class Solution {
public int[] bfsOrder(int n, int[][] edges) {
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
for (int[] e : edges) {
adj.get(e[0]).add(e[1]);
adj.get(e[1]).add(e[0]);
}
for (List<Integer> nb : adj) Collections.sort(nb);
boolean[] seen = new boolean[n];
int[] order = new int[n];
int count = 0;
ArrayDeque<Integer> q = new ArrayDeque<>();
for (int s = 0; s < n; s++) {
if (seen[s]) continue;
seen[s] = true;
q.add(s);
while (!q.isEmpty()) {
int u = q.poll();
order[count++] = u;
for (int v : adj.get(u))
if (!seen[v]) { seen[v] = true; q.add(v); } // mark on enqueue
}
}
return order;
}
}function bfsOrder(n, edges) {
const adj = Array.from({ length: n }, () => []);
for (const [u, v] of edges) { adj[u].push(v); adj[v].push(u); }
for (const nb of adj) nb.sort((a, b) => a - b);
const seen = new Array(n).fill(false);
const order = [];
const q = new Array(n);
let head = 0, tail = 0; // an array used as a queue: O(1) at both ends
for (let s = 0; s < n; s++) {
if (seen[s]) continue;
seen[s] = true;
q[tail++] = s;
while (head < tail) {
const u = q[head++];
order.push(u);
for (const v of adj[u]) {
if (!seen[v]) { seen[v] = true; q[tail++] = v; } // mark on enqueue
}
}
}
return order;
}use std::collections::VecDeque;
fn solve(n: i64, edges: &[[i64; 2]]) -> Vec<i64> {
let n = n as usize;
let mut adj = vec![Vec::new(); n];
for e in edges {
let (u, v) = (e[0] as usize, e[1] as usize);
adj[u].push(v);
adj[v].push(u);
}
for nb in adj.iter_mut() {
nb.sort();
}
let mut seen = vec![false; n];
let mut order = Vec::with_capacity(n);
let mut q = VecDeque::new();
for s in 0..n {
if seen[s] {
continue;
}
seen[s] = true;
q.push_back(s);
while let Some(u) = q.pop_front() {
order.push(u as i64);
for &v in &adj[u] {
if !seen[v] {
seen[v] = true; // mark on enqueue
q.push_back(v);
}
}
}
}
order
}import Data.List (foldl', sort)
import qualified Data.Map.Strict as M
import qualified Data.Set as S
import Data.Sequence (Seq (..), (|>))
import qualified Data.Sequence as Q
solve :: Int -> [[Int]] -> [Int]
solve n edges = reverse (snd (foldl' start (S.empty, []) [0 .. n - 1]))
where
adj = M.map sort (M.fromListWith (++) (concat [[(u, [v]), (v, [u])] | [u, v] <- edges]))
start acc@(seen, order) s
| S.member s seen = acc
| otherwise = bfs (Q.singleton s) (S.insert s seen) order
-- the queue is a Data.Sequence: O(1) at both ends
bfs Empty seen order = (seen, order)
bfs (u :<| rest) seen order =
let fresh = [v | v <- M.findWithDefault [] u adj, not (S.member v seen)]
seen' = foldl' (flip S.insert) seen fresh -- mark on enqueue
in bfs (foldl' (|>) rest fresh) seen' (u : order)
Variants to try
- Return the distance of every node from node 0, with −1 for nodes it cannot reach.
- Return the nodes grouped level by level as a list of lists.
- Return a shortest path from node 0 to node n − 1 by recording, for each node, the node it was reached from.
Next problems
Depth-first order of a graph
Return the order in which a depth-first traversal first reaches every node, restarting on each part of a disconnected graph.
Open the problem → Easy12 minCount the islands in a grid
Every unvisited land cell starts a new island; flood it so it is never counted again. The first graph problem most interviews ask.
Open the problem → Easy12 minDoes an undirected graph contain a cycle?
Return true if the graph has a cycle: a search that meets a visited node by any edge but the one it came along has found one.
Open the problem →Write a solution and run it against the real test table.
Keep going
Depth-first order of a graph
Return the order in which a depth-first traversal first reaches every node, restarting on each part of a disconnected graph.
Open the problem → Easy12 minCount the islands in a grid
Every unvisited land cell starts a new island; flood it so it is never counted again. The first graph problem most interviews ask.
Open the problem → Easy12 minDoes an undirected graph contain a cycle?
Return true if the graph has a cycle: a search that meets a visited node by any edge but the one it came along has found one.
Open the problem → GraphsThe pattern behind it
Grids, networks and dependencies are all graphs. Breadth-first search finds shortest paths, depth-first search and union-find find what is connected, Dijkstra handles weighted edges, and a queue of nodes with no remaining prerequisites gives an order.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.