⚙️AlgorithmIntermediate

0-1 BFS

Key Points

  • 0-1 BFS is a shortest path algorithm specialized for graphs whose edge weights are only 0 or 1.
  • It uses a deque: pus when the edge weight is 0, and pus when the edge weight is 1.
  • Because each vertex is processed at most once per successful relaxation, the algorithm runs in O(V + E) time.
  • 0-1 BFS is ideal for grid problems where moving onto certain cells is free while others cost 1 (e.g., walls to break).
  • It is a practical, faster alternative to Dijkstra’s algorithm for weights, avoiding a priority queue.
  • Distances are nondecreasing when popped from the front of the deque, ensuring correctness like Dijkstra.
  • It generalizes standard BFS (which is the special case where all edges have weight 0 or 1 but identical).
  • Do not use 0-1 BFS if weights exceed 1 or are negative; use Dijkstra (nonnegative) or Bellman-Ford/SPFA (can handle negatives).

Prerequisites

  • Graphs and adjacency listsYou must know how to represent graphs to iterate over neighbors efficiently and achieve O(V + E) complexity.
  • BFS (Breadth-First Search)0-1 BFS generalizes BFS by exploring in nondecreasing cost layers instead of equal-cost layers.
  • Dijkstra’s algorithm basicsUnderstanding why a priority queue is needed for general weights clarifies why a deque suffices for 0/1 weights.
  • Big-O notationTo analyze and compare the O(V + E) performance guarantee.
  • Shortest path relaxationCore step common to all shortest path algorithms; necessary for correctness reasoning.

Detailed Explanation

Tap terms for definitions

01Overview

Hook: Imagine navigating a city where some roads are fast lanes (free to traverse) and others have a small toll of exactly one unit. You want to reach your destination with the least total toll. Concept: 0-1 BFS is a shortest path algorithm tailored precisely for this setting—graphs where each edge weight is either 0 or 1. Instead of a priority queue (as in Dijkstra), it cleverly uses a double-ended queue (deque) to keep the next best nodes at the front. Example: In a grid maze, stepping onto empty cells might be free (0) while stepping through a door costs 1. 0-1 BFS will find the path with the fewest doors to open.

More formally, 0-1 BFS maintains a distance array and relaxes edges like Dijkstra. When it finds a zero-cost edge to a neighbor, it puts that neighbor at the front of the deque (so it’s processed immediately); when it finds a cost-one edge, it appends the neighbor at the back (to be processed later). This mirrors the behavior of an implicit two-level priority structure without the log-factor overhead.

Because each edge is considered at most once for a successful relaxation and deque operations are O(1), the whole algorithm runs in O(V + E) time and O(V) space. This makes 0-1 BFS a go-to tool in competitive programming (CF 1400–1800 range) for problems like minimal obstacle removals, binary-weighted networks, and layered transitions with free vs. costly moves.

02Intuition & Analogies

Hook: Picture a line at an amusement park with two special rules. If you get a VIP stamp (weight 0), you move to the very front of the line. If you don’t (weight 1), you wait at the back like everyone else. Concept: The deque in 0-1 BFS is that line. Whenever you can move along a free edge, you jump to the front; if the move costs 1, you join the back. This way, people with fewer total waiting costs always get served first—just like exploring paths with smaller distances first.

Analogy: Think of playing a board game where moving onto certain squares is free (like sliding on ice) and others consume exactly one energy point (like stepping over a small fence). If you always explore the free slides immediately (front of deque) and postpone the fence steps slightly (back of deque), you naturally prioritize cheaper total paths without needing a full-blown calculator (priority queue).

Example: In a grid puzzle, suppose moving into a clear cell is free and entering a rough cell costs one. Start at the origin with distance 0. From each position, any free step is explored right away (pushed to front), so all positions reachable at the same cost are expanded before any position with a higher cost is considered. Only when no free expansions remain do we take a step that increases the cost by 1. This layered expansion mimics BFS levels, except levels are “cost layers” rather than “edge-count layers.” That’s why 0-1 BFS generalizes BFS: it preserves the spirit of expanding by increasing cost layers using only a deque.

03Formal Definition

We are given a directed or undirected graph G = (V, E) with a weight function w: E → {0, 1}. For a source vertex s ∈ V, we want distances d[v] that satisfy the shortest-path optimality conditions. Initialize d[s] = 0 and d[v] = +∞ for . Maintain a deque D initially containing only s. While D is not empty, pop u from the front. For every edge (u, v) with weight w(u, v) ∈ {0, 1}, attempt relaxation: if d[v] > d[u] + w(u, v), then set d[v] = d[u] + w(u, v). If w(u, v) = 0, push v to the front of D; else push v to the back of D. This process ensures that vertices are popped in nondecreasing order of distance. Correctness follows from the fact that whenever a relaxation occurs via a 0-edge, the new vertex is processed before any vertex with larger distance; via a 1-edge, it is scheduled after all vertices with the current distance but before those with strictly larger distances. The algorithm terminates when D is empty, at which point all d[v] are final minimal distances. Time complexity is O( + ) because each edge can relax at most once and deque operations are O(1).

04When to Use

  • Binary-weight graphs: Any shortest path problem where every edge weight is either 0 or 1. Examples include toggling states with a free or paid operation, or moving through different types of transitions with a small fixed penalty.
  • Grid problems: Minimal walls/obstacles to remove, where moving into an empty cell is 0 and into a wall is 1. This captures common CF tasks like “break minimum walls to reach target.”
  • Layered states: Problems modeling states as nodes where some transitions are free (state reuse, same layer) and others cost 1 (move to next layer), such as bitmask DP transitions encoded as a graph.
  • Dijkstra alternative: Use 0-1 BFS instead of Dijkstra to remove the O(\log |V|) priority queue overhead when weights are strictly 0/1. This yields O(|V| + |E|), typically faster in practice.
  • Multi-source starts: When multiple sources have distance 0 (e.g., many starting portals), initialize the deque with all sources; 0-1 BFS naturally handles this.

Avoid 0-1 BFS if weights exceed 1, are non-integers, or negative. Use Dijkstra for general nonnegative weights and Bellman-Ford/SPFA variants if negative weights exist (with caution about performance).

⚠️Common Mistakes

  • Using 0-1 BFS for weights beyond {0, 1}: The deque trick relies on two buckets (front/back). If weights include 2 or more, the method breaks. Switch to Dial’s algorithm (bucketed Dijkstra) for small integer weights, or standard Dijkstra for general nonnegative weights.
  • Forgetting proper relaxation: Use “if d[v] > d[u] + w(u, v)” not “>=”. Using “>=” can create unnecessary pushes and even infinite loops with equal distances.
  • Not initializing distances to +∞ and sources to 0: Incorrect initial values lead to wrong layering and missed updates.
  • Marking visited too early: Do not mark vertices as permanently visited when first seen. In 0-1 BFS, a later 0-edge can improve a vertex’s distance. Use the distance check to control reprocessing; a node is effectively finalized once it’s popped with its minimal distance (which the algorithm ensures).
  • Ignoring graph direction or multiple edges: Always relax all edges from u, respecting direction. Multiple parallel edges with different weights are fine; relax each.
  • Forgetting to handle disconnected vertices: Unreachable nodes should remain at +∞ (or a sentinel like -1 in output). Don’t print garbage values.
  • Overflowing distances: Use a large sentinel like 1e9 for int distances or use long long if paths may have up to |E| ≈ 2e5 causing sums to fit comfortably.

Key Formulas

Relaxation Rule

Explanation: When exploring edge (u, v), we can improve the tentative distance to v by going through u. This is the core step that drives all shortest-path algorithms, including 0-1 BFS.

Time Complexity

Explanation: 0-1 BFS processes each vertex and edge a constant number of times with O(1) deque operations, leading to linear time in the size of the graph.

Grid Size Bounds

Explanation: For an n-by-m grid with 4-neighborhood adjacency, the number of vertices is nm and each cell has at most 4 edges, yielding at most 4nm edges.

Feasible Potentials

Explanation: Distances satisfy the triangle inequality along edges and start from zero at the source. Any shortest-path solution must respect these inequalities.

Binary Weights Constraint

Explanation: 0-1 BFS requires every edge weight to be exactly 0 or 1. Violating this assumption breaks the deque scheduling logic.

Edge Accounting

Explanation: Each edge can cause at most one successful relaxation that inserts a node into the deque, bounding the total number of insertions by the number of edges.

Distance Range

Explanation: With weights 0 or 1, any shortest path has integral cost between 0 and the number of edges, which bounds array sizes and supports bucket-like data structures.

Priority Queue vs Deque

Explanation: Replacing a binary heap (Dijkstra) with a deque under weights removes the log factor, yielding linear time in practice and theory.

Complexity Analysis

0-1 BFS achieves linear time complexity T(,) = O( + ). The key reason is that each vertex can only be inserted into the deque upon a successful relaxation that strictly decreases its distance, and each directed edge can cause at most one such decrease. Consequently, the total number of successful relaxations is at most . For each vertex pop from the deque, we scan its adjacency list once, accounting for the total O() edge traversals across the algorithm. All deque operations (pus, pus, po) are O(1), so they do not introduce any log factors. Space complexity is O( + ) when using an adjacency list: O() to store the graph and O() for the distance array, optional parent array (for path reconstruction), and the deque. On grids with n×m cells, = nm and ≤ 4nm for 4-neighborhoods, leading to O(nm) time and space in that setting. Compared to Dijkstra’s O(( + ) log ) with a binary heap, 0-1 BFS is asymptotically faster for binary weights because the deque effectively acts as a two-bucket priority queue. If edge weights exceed 1 or are not integers, this performance guarantee no longer holds, and 0-1 BFS may fail to return correct answers; then Dijkstra or bucketed variants (e.g., Dial’s algorithm for small integer weights) should be used.

Code Examples

Generic 0-1 BFS on a graph with weights in {0,1}
1#include <bits/stdc++.h>
2using namespace std;
3
4// Solves single-source shortest paths on a graph with edge weights 0 or 1
5// Input format example:
6// n m s
7// u v w (m lines, 0-indexed vertices, weight w in {0,1})
8// Prints distances from s to every vertex (INF if unreachable)
9
10int main() {
11 ios::sync_with_stdio(false);
12 cin.tie(nullptr);
13
14 int n, m, s;
15 if (!(cin >> n >> m >> s)) return 0;
16 vector<vector<pair<int,int>>> g(n);
17 for (int i = 0; i < m; ++i) {
18 int u, v, w; cin >> u >> v >> w;
19 // Directed edge u -> v with weight w in {0,1}
20 g[u].push_back({v, w});
21 // If the graph is undirected, also add g[v].push_back({u, w});
22 }
23
24 const int INF = 1e9;
25 vector<int> dist(n, INF);
26 deque<int> dq;
27
28 dist[s] = 0;
29 dq.push_front(s);
30
31 while (!dq.empty()) {
32 int u = dq.front(); dq.pop_front();
33 for (auto [v, w] : g[u]) {
34 // Relaxation: try to improve dist[v]
35 if (dist[v] > dist[u] + w) {
36 dist[v] = dist[u] + w;
37 if (w == 0) dq.push_front(v); else dq.push_back(v);
38 }
39 }
40 }
41
42 for (int i = 0; i < n; ++i) {
43 if (dist[i] == INF) cout << "INF\n"; else cout << dist[i] << "\n";
44 }
45 return 0;
46}
47

This program builds a directed graph with binary weights and runs 0-1 BFS from source s. When a 0-weight relaxation occurs, the neighbor is pushed to the front (processed sooner); for a 1-weight relaxation, it’s pushed to the back. Distances are finalized in nondecreasing order, yielding correct shortest paths in O(V + E).

Time: O(V + E)Space: O(V + E)
Grid: minimum walls to break from (0,0) to (n-1,m-1)
1#include <bits/stdc++.h>
2using namespace std;
3
4// Given an n x m grid of characters:
5// '.' means free cell (entering costs 0)
6// '#' means wall (entering costs 1)
7// Move in 4 directions. Start at (0,0), target at (n-1,m-1).
8// Find the minimum number of walls to break to reach the target.
9
10int main() {
11 ios::sync_with_stdio(false);
12 cin.tie(nullptr);
13
14 int n, m;
15 if (!(cin >> n >> m)) return 0;
16 vector<string> grid(n);
17 for (int i = 0; i < n; ++i) cin >> grid[i];
18
19 const int INF = 1e9;
20 vector<vector<int>> dist(n, vector<int>(m, INF));
21
22 auto inside = [&](int r, int c){ return r >= 0 && r < n && c >= 0 && c < m; };
23
24 deque<pair<int,int>> dq;
25 dist[0][0] = (grid[0][0] == '#'); // cost to enter start cell (often 0 if ensured '.')
26 dq.push_front({0,0});
27
28 int dr[4] = {-1, 1, 0, 0};
29 int dc[4] = {0, 0, -1, 1};
30
31 while (!dq.empty()) {
32 auto [r, c] = dq.front(); dq.pop_front();
33 for (int k = 0; k < 4; ++k) {
34 int nr = r + dr[k], nc = c + dc[k];
35 if (!inside(nr, nc)) continue;
36 int w = (grid[nr][nc] == '#') ? 1 : 0; // cost to enter next cell
37 if (dist[nr][nc] > dist[r][c] + w) {
38 dist[nr][nc] = dist[r][c] + w;
39 if (w == 0) dq.push_front({nr, nc}); else dq.push_back({nr, nc});
40 }
41 }
42 }
43
44 int ans = dist[n-1][m-1];
45 if (ans >= INF) cout << -1 << "\n"; else cout << ans << "\n";
46 return 0;
47}
48

Cells are nodes; edges connect 4-neighbors. Entering a wall cell has weight 1, entering a free cell has weight 0. Using 0-1 BFS over the implicit grid graph yields the minimal number of walls to break from start to goal in O(nm).

Time: O(nm)Space: O(nm)
0-1 BFS with multi-source start and path reconstruction
1#include <bits/stdc++.h>
2using namespace std;
3
4// Multi-source 0-1 BFS: distances from any source in S to all nodes, with path reconstruction to target t.
5// Input example:
6// n m k
7// s1 s2 ... sk (k sources)
8// u v w (m lines, directed, w in {0,1})
9// t (target)
10
11int main() {
12 ios::sync_with_stdio(false);
13 cin.tie(nullptr);
14
15 int n, m, k;
16 if (!(cin >> n >> m >> k)) return 0;
17 vector<int> sources(k);
18 for (int i = 0; i < k; ++i) cin >> sources[i];
19 vector<vector<pair<int,int>>> g(n);
20 for (int i = 0; i < m; ++i) {
21 int u, v, w; cin >> u >> v >> w;
22 g[u].push_back({v, w});
23 }
24 int t; cin >> t;
25
26 const int INF = 1e9;
27 vector<int> dist(n, INF), par(n, -1), parW(n, -1);
28 deque<int> dq;
29
30 // Initialize all sources with distance 0
31 for (int s : sources) {
32 if (dist[s] > 0) {
33 dist[s] = 0;
34 dq.push_front(s);
35 }
36 }
37
38 while (!dq.empty()) {
39 int u = dq.front(); dq.pop_front();
40 for (auto [v, w] : g[u]) {
41 if (dist[v] > dist[u] + w) {
42 dist[v] = dist[u] + w;
43 par[v] = u; // store predecessor for path reconstruction
44 parW[v] = w; // store edge weight used
45 if (w == 0) dq.push_front(v); else dq.push_back(v);
46 }
47 }
48 }
49
50 if (dist[t] >= INF) {
51 cout << "No path\n";
52 return 0;
53 }
54
55 // Reconstruct one shortest path to t
56 vector<int> path;
57 for (int v = t; v != -1; v = par[v]) path.push_back(v);
58 reverse(path.begin(), path.end());
59
60 cout << "Distance: " << dist[t] << "\n";
61 cout << "Path (" << path.size() << " nodes): ";
62 for (int i = 0; i < (int)path.size(); ++i) cout << path[i] << (i+1==(int)path.size()? '\n' : ' ');
63
64 // Optional: print edge weights along the path
65 cout << "Edge weights along path: ";
66 for (int i = 1; i < (int)path.size(); ++i) cout << parW[path[i]] << (i+1==(int)path.size()? '\n' : ' ');
67
68 return 0;
69}
70

This variant initializes multiple sources at distance 0 and uses the same deque logic. It also stores parent pointers to reconstruct one shortest path to a given target t. Parent and edge-weight arrays are updated upon successful relaxation so the reconstructed path aligns with the minimal distance.

Time: O(V + E)Space: O(V + E)