← Wiki Index / Computation & Algorithms / d-ary Heap
💻 Computation & Algorithms

d-ary Heap

A d-ary heap is an implicit array-backed priority queue data structure introduced by Donald B. Johnson that generalizes the binary heap to branching factor d ≥ 2, trading shallower O(logd n) sift-up operations against O(d logd n) sift-down operations.

A d-ary heap (also called a d-heap) is an implicit, array-backed priority queue data structure within computation and algorithms that generalizes the classical binary heap by allowing every internal node in a nearly complete tree to have up to d children (for a fixed integer branching factor d ≥ 2) instead of at most 2. Introduced by Donald B. Johnson in 1975 in Information Processing Letters, the d-ary heap stores n elements contiguously inside a flat array A[0 .. n − 1] with zero explicit parent or child pointers, resolving all tree navigation via simple integer index arithmetic while enforcing the standard min-heap (or max-heap) order invariant that every node's key is less than or equal to the keys of all d of its children.

Increasing the branching factor from 2 to d compresses the height of an n-node heap from ⌊log2 n⌋ down to ⌈logd((d − 1)n + 1)⌉ − 1 = Θ(logd n) = Θ((log n) / (log d)). Because upward restoration of the heap invariant (sift-up, invoked by insert and decrease-key) compares a node against only its single parent at each level, a d-ary heap reduces the worst-case comparison count of insert and decrease-key by a factor of log2 d to O(logd n). Conversely, downward restoration (sift-down, invoked by delete-min) must identify the smallest among all d children at each level, taking O(d logd n) comparisons. In workloads where decrease-key operations outnumber delete-min operations—most notably Dijkstra's algorithm for single-source shortest paths and Prim's algorithm for minimum spanning trees—setting d = 4 (a quaternary heap) or d = 8 (an octary heap) yields both theoretical speedups on dense graphs and substantial wall-clock gains on modern cache-hierarchical CPUs.

Implicit Array Representation and Tree Geometry

Zero-Indexed Parent and Child Arithmetic

Like a binary heap, a d-ary heap is a nearly complete d-ary tree: every level above the bottom level is completely filled with d0, d1, d2, …, dh−1 nodes, and all nodes on the bottom level h are packed as far to the left as possible. When the n nodes are laid out in level-order (breadth-first order) inside a 0-indexed array A[0 .. n − 1] with the root at index 0, the parent and children of any index i are given directly by:

  • Parent index of node i (for i ≥ 1):
parent(i) = ⌊(i − 1) / d⌋
  • c-th child index of node i (for c ∈ {1, 2, …, d}):
child(i, c) = d · i + c

Thus the contiguous block of up to d children of node i occupies the consecutive array slice:

A[d · i + 1 .. min(d · i + d, n − 1)]

When the branching factor is chosen as a power of two, d = 2k (such as d = 4 with k = 2, or d = 8 with k = 3), integer division and multiplication by d compile to single-cycle bitwise shifts: parent(i) = (i − 1) ≫ k and child(i, 1) = (i ≪ k) + 1.

precise Height Bound

A complete d-ary tree of height h (where the root has depth 0 and leaves have depth h) contains at most:

∑j=0..h dj = (dh+1 − 1) / (d − 1)

nodes. Inverting this geometric series for an n-element d-ary heap establishes the precise height:

h(n, d) = ⌈logd((d − 1)n + 1)⌉ − 1 = ⌊logd((d − 1)n)⌋

For example, storing n = 1,000,000 keys in a binary heap (d = 2) produces a tree of height 19, whereas a quaternary heap (d = 4) has height 9 and an octary heap (d = 8) has height 6.

Core Priority Queue Operations and Complexity Trade-Offs

Sift-Up (Insert and Decrease-Key) vs. Sift-Down (Delete-Min)

In a min-oriented d-ary heap, all priority queue operations reduce to two structural primitives:

  1. sift-up(i) (insert and decrease-key): When a new element is appended at A[n] or an existing key at A[i] is decreased, it may violate the heap invariant relative to A[parent(i)]. The algorithm repeatedly compares A[i] against A[⌊(i − 1) / d⌋] and moves the parent down if A[i] < A[parent(i)]. Because each node has only one parent, each level along the upward path requires exactly 1 key comparison, for a worst-case total of at most:
Csift-up(n, d) = h(n, d) ≈ (log2 n) / (log2 d) = O(logd n)
  1. sift-down(i) (delete-min and increase-key): Removing the minimum at A[0] replaces the root with the last element A[n − 1] and sifts it downward. At each internal node i along the descent path, the algorithm must first find the minimum key among all d children A[d · i + 1 .. d · i + d] (requiring d − 1 comparisons among the children) and then compare that smallest child against the element being sifted (1 additional comparison). Thus each level of descent performs up to d comparisons, giving a worst-case total of:
Csift-down(n, d) = d · h(n, d) ≈ (d / log2 d) log2 n = O(d logd n)
  1. Bottom-up build-heap in O(n) time: Applying Floyd's bottom-up heapification from the last internal node index ⌊(n − 2) / d⌋ down to 0 sifts down at most ⌈n / dj+1⌉ nodes through j levels at cost d · j. Summing over all levels yields at most:
∑j=1..h (n / dj+1) · d · j = n ∑j=1..h (j / dj) ≤ (d / (d − 1)2) · n = O(n)

Notice that for d ≥ 3, the leading constant d / (d − 1)2 on bottom-up build-heap is actually smaller than the binary heap constant of 2 (specifically 3/4 = 0.75n comparisons for d = 3, and 4/9 ≈ 0.44n comparisons for d = 4), because at least (d − 1) / d of all nodes in a d-ary heap are leaves that require zero work.

Priority Queue Structurefind-mininsert (Worst)decrease-keydelete-minbuild-heapPointer Overhead
Binary Heap (d = 2)O(1)O(log2 n)⌊log2 n⌋2⌊log2 n⌋2n (O(n))0 (implicit array)
Quaternary Heap (d = 4)O(1)O(log4 n) = 0.5 log2 n0.5⌊log2 n⌋2⌊log2 n⌋(4/9)n ≈ 0.44n0 (implicit array)
General d-ary HeapO(1)O(logd n)O((log n) / (log d))O(d (log n) / (log d))O(n)0 (implicit array)
Binomial HeapO(log n) or O(1)O(log n) (O(1) amort.)O(log n)O(log n)O(n)3 pointers / node
Fibonacci HeapO(1)O(1)O(1) amortizedO(log n) amortizedO(n)4 pointers + 1 bit
Pairing HeapO(1)O(1)o(log n) amortizedO(log n) amortizedO(n)2–3 pointers / node

Optimization of Graph Algorithms and CPU Cache Behavior

Balancing Dijkstra's and Prim's Algorithms on Dense Graphs

In Dijkstra's algorithm and Prim's algorithm on a connected graph G = (V, E) with n = |V| vertices and m = |E| edges, the priority queue executes at most n insert operations, n delete-min operations, and up to m decrease-key operations. Using a d-ary heap, the total priority-queue running time is:

T(n, m, d) = O(n · d logd n + m · logd n) = O(((n · d + m) · log n) / (log d))

To equalize the two terms n · d and m, Donald B. Johnson (1975) and Robert E. Tarjan (1983) set the branching factor dynamically as a function of the graph's average degree:

d = max(2, ⌈m / n⌉)

With d = max(2, ⌈m / n⌉), the total running time of Dijkstra's and Prim's algorithms simplifies to:

T(n, m) = O(m · logmax(2, ⌈m/n⌉) n)

Whenever the graph is moderately dense—specifically whenever m ≥ n1+ε for any constant ε > 0—we have logm/n n ≤ 1 / ε = O(1), so a simple array-backed d-ary heap achieves linear O(m) running time on dense graphs, matching the asymptotic bound of a Fibonacci heap or Brodal queue without any pointer chasing or dynamic node allocation.

Why Quaternary (d = 4) and Octary (d = 8) Heaps Dominate in Practice

Notice from the sift-down bound (d / log2 d) log2 n that the factor d / log2 d takes the precise same value 2 at both d = 2 (since 2 / log2 2 = 2) and d = 4 (since 4 / log2 4 = 4 / 2 = 2). Consequently, moving from a binary heap (d = 2) to a quaternary heap (d = 4) incurs zero extra comparisons in the worst case of delete-min while cutting the number of comparisons and memory writes in insert and decrease-key in half and halving the number of vertical tree levels traversed during delete-min.

On modern x86-64 and ARM64 processors with 64-byte L1/L2 cache lines, a quaternary or octary heap offers three additional hardware advantages demonstrated by LaMarca and Ladner (1996):

  1. Sibling spatial locality: All d = 4 (or d = 8) children of node i reside contiguously in A[d · i + 1 .. d · i + d]. When each heap entry is an 8-byte (key, vertex) pair or a 16-byte struct, all 4 (or 8) siblings fit inside a single 64-byte cache line. Finding the minimum child during sift-down therefore triggers only 1 cache-line fetch per level instead of 2 levels of dependent cache misses in a binary heap.
  2. SIMD horizontal min-reduction: Because the 4 or 8 children sit in adjacent array slots, finding their minimum can be executed branchlessly using a single SSE/AVX or NEON vector comparison instruction.
  3. Cache-aligned padding: By placing the root at index 3 (or padding the array start by d − 1 dummy slots), the block of d children A[d · i .. d · i + d − 1] of every node i aligns on an precise multiple of d elements, guaranteeing that no sibling group ever straddles a 64-byte cache-line boundary.

Frequently Asked Questions

Why does a 4-ary heap perform the same number of worst-case comparisons in delete-min as a binary heap?

In a binary heap (d = 2) of height log2 n, each level of sift-down performs 1 comparison between the two children and 1 comparison against the sifted element, totaling 2 log2 n comparisons. In a 4-ary heap (d = 4), the height is halved to log4 n = (1/2) log2 n, while each level performs 3 comparisons among the 4 children plus 1 comparison against the sifted element (4 comparisons per level). Multiplying 4 comparisons per level by (1/2) log2 n levels yields the identical product 2 log2 n, while cutting the number of vertical cache misses and array swaps in half.

Can Floyd's bottom-up bounce heuristic be applied to d-ary heaps?

Yes. Just as Floyd's bounce optimization in a binary heap sifts a vacated root all the way down to a leaf using only child-to-child comparisons (d − 1 per level) and then sifts the replacement element upward (1 comparison per level), applying the same two-phase strategy to a d-ary heap reduces the delete-min comparison count from d logd n down to (d − 1) logd n + O(1).

When should a binary heap or weak heap be preferred over a d-ary heap?

When the only operations performed are build-heap followed by n consecutive delete-min operations (as in pure in-place Heapsort with zero decrease-key calls) and key comparisons themselves are expensive (such as comparing long strings or multi-field database tuples rather than 64-bit integers), a weak heap or bottom-up binary heap performs ≈ n log2 n comparisons, whereas a d-ary heap with d ≥ 4 performs at least 1.5n log2 n comparisons.

References

  1. Johnson, D. B. (1975). "Priority queues with update and finding minimum spanning trees." Information Processing Letters, 4(3), 53–57. https://doi.org/10.1016/0020-0190(75)90001-090001-0)
  2. Tarjan, R. E. (1983). Data Structures and Network Algorithms (Chapter 3: "Heaps," pp. 33–43). Society for Industrial and Applied Mathematics (SIAM). https://doi.org/10.1137/1.9781611970265
  3. LaMarca, A., & Ladner, R. E. (1996). "The influence of caches on the performance of heaps." ACM Journal of Experimental Algorithmics, 1, Article 4. https://doi.org/10.1145/235141.235145
  4. Larkin, D. H., Sen, S., & Tarjan, R. E. (2014). "A back-to-basics empirical study of priority queues." In Proceedings of the Sixteenth Workshop on Algorithm Engineering and Experiments (ALENEX 2014), 61–72. https://doi.org/10.1137/1.9781611973198.7
🧬 Support Open Science
Explore interactive science games on LowKey Games or browse scientific apparel & accessories in the CRV Science Shop.
💡 Spot an inaccuracy or have primary literature to expand this article?
Submit a citation or revision note using your shared CRV Science / LowKey Games profile ID.
Suggest Revision