Purpose: Shortest paths when all edge weights are small non-negative integers , in — replacing Dijkstra’s priority queue with an array of buckets.

The Idea

Dijkstra spends per operation maintaining a heap ordered by distance. If distances are bounded by , you can instead keep an array bucket[0..D] where bucket[d] holds the vertices at tentative distance , and sweep upward. Each bucket access is .

vector<int> dial(int n, int s, vector<vector<pair<int,int>>>& adj, int C) {
    int D = (n - 1) * C;
    vector<int> dist(n, INT_MAX);
    vector<vector<int>> bucket(D + 1);
    dist[s] = 0;
    bucket[0].push_back(s);
 
    for (int d = 0; d <= D; d++) {
        while (!bucket[d].empty()) {
            int u = bucket[d].back(); bucket[d].pop_back();
            if (dist[u] != d) continue;                 // stale entry
            for (auto [v, w] : adj[u])
                if (d + w < dist[v]) {
                    dist[v] = d + w;
                    bucket[d + w].push_back(v);
                }
        }
    }
    return dist;
}

Circular buckets — the memory fix

The full array is , which can be huge. But at any moment the live distances span only , so buckets suffice if you index them modulo :

vector<vector<int>> bucket(C + 1);
// insert v at distance nd:  bucket[nd % (C + 1)].push_back(v);

This brings the space down to and is the version worth writing.

Complexity comparison

WeightsAlgorithmTime
all 1BFS
0-1 BFS
integers, smallDial or with circular buckets
arbitrary non-negativeDijkstra
arbitrary, Fibonacci heapDijkstra
integers, word RAMThorup — theoretical

Dial wins when roughly; beyond that the bucket sweep costs more than the heap.

Where it appears

  • Small integer costs — grids where each move costs 1-5, edge colours with small penalties
  • Radix heaps — the generalisation: buckets by the highest differing bit, giving Dijkstra
  • Timetable and routing problems with discretised times
  • 0-1 BFS — the special case, where two buckets (a deque) suffice

Practical note

In a contest, Dijkstra with priority_queue handles in well under a second, so Dial is rarely necessary. Its value is knowing that “the priority queue is the only slow part” and that bounded weights let you delete it. The same insight produces the deque in 0-1 BFS, which is commonly needed.

See also: 0-1 BFS · Dijkstra · Shortest Paths