10.10 Solving 0-1 Shortest Path Problems with 0-1 BFS
Between ordinary BFS and Dijkstra lies an interesting class of shortest-path problems.
10.10 Solving 0-1 Shortest Path Problems with 0-1 BFS
Between ordinary BFS and Dijkstra lies an interesting class of shortest-path problems.
Suppose edge weights are restricted to only two values:
0
1
Many practical problems naturally have this structure.
A routing system may assign cost 0 to preferred roads and cost 1 to secondary roads. A state-space search may assign cost 0 to free transitions and cost 1 to costly transitions. Grid problems often assign cost 0 to moving in a preferred direction and cost 1 to moving elsewhere.
Dijkstra solves these problems correctly, but it uses a priority queue. Since edge weights are limited to 0 and 1, we can exploit that structure to obtain a simpler and often faster algorithm.
This technique is called 0-1 BFS.
Problem
Given a graph whose edge weights are either:
0
or
1
compute the shortest path from a source vertex to every other vertex.
For example:
0 --0--> 1
| |
1 0
| |
v v
2 --1--> 3
Starting from vertex 0, determine the minimum path cost to all vertices.
Solution
Instead of a priority queue, use a deque.
When relaxing:
- A weight-0 edge inserts at the front.
- A weight-1 edge inserts at the back.
package main
import (
"container/list"
"fmt"
)
type Edge struct {
To int
Weight int
}
func zeroOneBFS(graph [][]Edge, source int) []int {
const INF = int(1e18)
dist := make([]int, len(graph))
for i := range dist {
dist[i] = INF
}
dist[source] = 0
deque := list.New()
deque.PushFront(source)
for deque.Len() > 0 {
element := deque.Front()
deque.Remove(element)
v := element.Value.(int)
for _, edge := range graph[v] {
candidate := dist[v] + edge.Weight
if candidate >= dist[edge.To] {
continue
}
dist[edge.To] = candidate
if edge.Weight == 0 {
deque.PushFront(edge.To)
} else {
deque.PushBack(edge.To)
}
}
}
return dist
}
func main() {
graph := [][]Edge{
{{1, 0}, {2, 1}},
{{3, 0}},
{{3, 1}},
{},
}
fmt.Println(zeroOneBFS(graph, 0))
}
Output:
[0 0 1 0]
The shortest path to vertex 3 is:
0 -> 1 -> 3
with total cost:
0 + 0 = 0
Why a Deque Is Enough
Dijkstra uses a priority queue because arbitrary edge weights can produce arbitrary distance differences.
Suppose:
distance[v] = 10
and an edge has weight:
1000
The resulting distance becomes:
1010
A priority queue is necessary to maintain global ordering.
In a 0-1 graph, only two possibilities exist.
If:
distance[v] = d
then a neighboring vertex receives either:
d
or:
d + 1
Nothing else is possible.
Consequently, the deque always contains vertices whose distances differ by at most one.
This limited ordering is sufficient to preserve shortest-path correctness.
Visualizing the Queue
Suppose the deque currently contains:
[distance 3 vertices]
Processing a weight-0 edge produces:
distance 3
again.
That vertex belongs at the front.
Front
[3][3][3][4][4]
Back
Processing a weight-1 edge produces:
distance 4
which belongs at the back.
Front
[3][3][3][4][4][4]
Back
The deque naturally maintains distance order without requiring heap operations.
Understanding the Invariant
The key invariant resembles Dijkstra's algorithm.
At any moment:
Vertices near the front of the deque have the smallest known distances.
Weight-0 edges preserve distance.
Weight-1 edges increase distance by exactly one.
Because no larger jump is possible, inserting at the front or back is sufficient.
The deque effectively becomes a specialized priority queue with only two priority levels.
Relationship to BFS
Ordinary BFS assumes every edge has weight:
1
0-1 BFS extends this idea.
| Algorithm | Allowed Weights |
|---|---|
| BFS | 1 |
| 0-1 BFS | 0, 1 |
| Dijkstra | Any nonnegative weight |
You can think of 0-1 BFS as the natural bridge between BFS and Dijkstra.
The data structures evolve similarly:
BFS -> Queue
0-1 BFS -> Deque
Dijkstra -> Priority Queue
Each step supports a larger class of edge weights.
Path Reconstruction
Path reconstruction works exactly as in Dijkstra.
Maintain a parent array.
parent := make([]int, n)
for i := range parent {
parent[i] = -1
}
Whenever a better path is discovered:
dist[next] = candidate
parent[next] = current
The shortest path can later be reconstructed by following parent pointers backward.
No additional logic is required.
Grid Example
One of the most common uses of 0-1 BFS appears in grid problems.
Suppose each cell contains a preferred direction.
Moving in that direction costs:
0
Changing direction costs:
1
Example:
→ → ↓
↑ → ↓
↑ ↑ →
The objective is often:
Minimum number of direction changes
A graph can be constructed where:
- Preferred moves have weight 0.
- Nonpreferred moves have weight 1.
0-1 BFS then computes the optimal solution efficiently.
This pattern appears frequently in competitive programming and path-planning systems.
Discussion
The most important lesson from 0-1 BFS is not the algorithm itself.
It is the idea of exploiting problem structure.
General shortest-path algorithms solve broad classes of problems.
Specialized algorithms exploit additional constraints.
In this case:
Weights ∈ {0, 1}
is enough to eliminate the priority queue entirely.
This principle appears throughout algorithm design.
Every constraint is potentially an optimization opportunity.
Complexity
Each edge is relaxed at most a small number of times.
Deque operations are constant time.
| Operation | Complexity |
|---|---|
| Push front | O(1) |
| Push back | O(1) |
| Pop front | O(1) |
| Total runtime | O(V + E) |
| Memory usage | O(V + E) |
This matches ordinary BFS while supporting weighted edges.
Compared to Dijkstra:
| Algorithm | Complexity |
|---|---|
| BFS | O(V + E) |
| 0-1 BFS | O(V + E) |
| Dijkstra | O((V + E) log V) |
For 0-1 graphs, the improvement can be substantial.
Common Mistakes
Do not use a standard queue. Weight-0 edges must be processed before weight-1 edges.
Do not use 0-1 BFS when weights larger than 1 exist.
Even a single edge of weight:
2
invalidates the algorithm.
Do not assume the deque contains globally sorted distances. The correctness argument is more subtle and depends on the limited weight range.
Do not forget that weight-0 edges belong at the front.
Reversing the insertion logic destroys correctness.
Practical Applications
0-1 BFS appears in:
- Grid navigation problems
- Network routing
- State-space search
- String transformation problems
- Cost-minimization systems
- Path-planning algorithms
- Competitive programming contests
- Transportation optimization
Many problems that appear to require Dijkstra become linear-time solutions once the weight structure is recognized.
Takeaway
0-1 BFS exploits a simple but powerful observation: when edge weights are restricted to 0 and 1, a deque can replace a priority queue. Weight-0 edges are processed immediately from the front, while weight-1 edges are deferred to the back. The result is a shortest-path algorithm with linear complexity that bridges the gap between ordinary BFS and Dijkstra's algorithm.