Dial's Algorithm is Dijkstra's shortest-path algorithm with the priority queue swapped for a much simpler structure: an array of buckets, one per possible distance. When edge weights are small non-negative integers, this is faster and dead simple to code.
Problem. Given a directed graph with non-negative integer edge weights (each at most W) and a
source vertex, find the shortest distance from the source to every other vertex.
Example: vertices A..E, edges A→B (1), A→C (3), B→C (1), B→D (2), C→E (2), D→E (1),
source A → distances A=0, B=1, C=2, D=3, E=4.
The slow way first
Plain Dijkstra uses a binary heap to always pull out the nearest unsettled vertex. Each pop and each update costs O(log n), so the total is O((V + E) log V). The log factor comes entirely from keeping a sorted-ish priority queue.
The question to ask: do I really need a general heap? If every edge weight is a small integer, the largest distance is at most (n − 1) · W, a modest number. We can index vertices directly by their distance instead of comparing them.
The idea: one bucket per distance
Keep an array buckets where buckets[d] holds every vertex whose current tentative distance is exactly d. Start the source in buckets[0]. Then sweep the bucket index d upward from 0: the lowest non-empty bucket always contains the next vertex to settle, so popping it is the heap's job done in O(1). Relaxing an edge that improves a vertex just appends it into the bucket for its new, smaller distance.
Because we never move the sweep index d backward and a vertex only ever lands in a bucket at a distance we have not passed yet, the first time we pop a vertex its distance is final. Old copies sitting in higher buckets are simply skipped.
Walk through it
Step through the animation. A starts in bucket 0 with distance 0. We pop it, relax A→B and A→C, and B, C land in buckets 1 and 3. Watch C improve from 3 to 2 when we reach B — it just gets a fresh copy in bucket 2, and the stale bucket-3 copy is skipped later. The sweep keeps climbing until every bucket is empty.
Pseudocode
dist[source] = 0, all others = infinity
buckets = array of empty lists, size n*W + 1
buckets[0].append(source)
for d = 0, 1, 2, ... up to the last bucket:
while buckets[d] is not empty:
u = pop from buckets[d]
if d > dist[u]: # a stale, outdated copy
skip it
for each (v, w) leaving u:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
buckets[dist[v]].append(v)
return distThe Python solution
def dial(graph, source, W):
# graph[u] = list of (v, weight); W = max edge weight
n = len(graph)
dist = [INF] * n
dist[source] = 0
buckets = [[] for _ in range(n * W + 1)]
buckets[0].append(source)
for d in range(len(buckets)):
while buckets[d]:
u = buckets[d].pop()
if d > dist[u]: # stale entry, skip
continue
for v, w in graph[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
buckets[dist[v]].append(v)
return distdistholds the best distance known to each vertex; the source starts at 0 and the rest at infinity.bucketshas one slot per reachable distance,0throughn·W— that bound is what keeps the array finite.- The outer
for dloop is the heap replacement: it sweeps distances in increasing order, sobuckets[d]is always the lowest non-empty bucket when it has anything in it. if d > dist[u]discards stale copies — a vertex that was later improved is sitting in an earlier, smaller bucket already settled.- The relax block appends an improved vertex straight into
buckets[dist[v]], no comparisons needed.
Complexity
| Case | Time | Notes |
|---|---|---|
| Dijkstra with binary heap | O((V + E) log V) (moderate) | log from the priority queue |
| Dial (this solution) | O(V·W + E) (moderate) | sweep the bucket array once |
O(V·W) (moderate)When W is small, O(V·W + E) beats the log V heap and the code is shorter. The cost is the bucket array: its size scales with the maximum distance, so Dial only pays off for small integer weights.
When this pattern shows up
Whenever a shortest-path or scheduling problem has small non-negative integer weights, mention Dial's bucket queue. The same bucketing idea powers counting sort and radix sort: when keys come from a small integer range, an indexed array replaces a comparison-based structure and drops the log factor.
Dial only works for non-negative weights, and the bucket array has size n·W + 1. If W is huge,
that array is enormous and a plain heap is the better choice. Also remember to skip stale copies with the
d > dist[u] check, or you will relax a vertex from an outdated distance.
Practice
After popping A and relaxing its edges, B sits in bucket 1 and C in bucket 3. When we later pop B and relax B→C with weight 1, what happens to C?
1. What does buckets[d] hold in Dial's algorithm?
2. Why can Dial replace the binary heap?
3. Why does the code check 'if d > dist[u]'?
4. What is the running time of Dial's algorithm?