Algorithmics study and revision notes
2026-08-22
Some algorithms need the next item in arrival order, but many need the item with the best priority so far. An event simulator processes the earliest event, a scheduler selects the most urgent job, and Dijkstra’s and Prim’s algorithms repeatedly select the smallest tentative key. A priority queue provides this access pattern without keeping every item fully sorted.
The central design question is not simply “Which heap is fastest?” Different representations make insertion, minimum extraction, key changes, and melding cheap in different combinations. The right choice follows from the workload and from the cost model of the machine.
After studying this note, you should be able to:
A min-priority queue stores items carrying comparable keys. The item and its key need not be the same object. Its usual interface is:
insert(Q, item, key);minimum(Q), which observes but does not remove a
minimum item;extract_min(Q), which removes and returns one minimum
item;decrease_key(Q, handle, new_key); andmeld(Q1, Q2), which combines two queues.Duplicate keys require a stated policy but do not invalidate a heap:
any minimum-key item may be returned. A decrease_key
operation needs a handle, index map, or node reference; finding an
arbitrary item by value is a separate search problem.
A sorted linked list makes minimum and
extract_min constant-time but needs linear-time insertion.
An unsorted list reverses that trade-off. Heaps maintain only enough
order to expose an extremum efficiently.
A binary min-heap maintains two properties:
Shape invariant: it is a complete binary tree, with every level full except possibly the last, which is filled from left to right.
Heap-order invariant: for every non-root node ,
Completeness gives an implicit array representation with no pointers. For zero-based index ,
The root is a minimum: following parent links from any node to the root never increases the key. The array is not globally sorted; the relation between nodes in different subtrees is generally unknown.
Insertion first preserves the shape invariant by appending a leaf.
Only the new leaf-to-root path can violate heap order, so
sift_up repairs exactly that path.
insert(A, x):
append x to A
i = len(A) - 1
while i > 0:
p = (i - 1) // 2
if key(A[p]) <= key(A[i]): break
swap A[p], A[i]
i = p
For extract_min, save the root, move the last item to
index 0, and remove the last cell. Only a root-to-leaf path can now
violate heap order. At each step, swap with the smaller child; choosing
the larger child could leave the smaller child below an invalid
parent.
extract_min(A):
require len(A) > 0
answer = A[0]
A[0] = A[-1]
remove the last array cell
i = 0
while left(i) < len(A):
c = left(i)
if right(i) < len(A) and key(A[right(i)]) < key(A[c]):
c = right(i)
if key(A[i]) <= key(A[c]): break
swap A[i], A[c]
i = c
return answer
Each repair crosses at most the tree height
,
so insertion, extraction, and decrease-key take
;
minimum takes
.
A d-ary heap, for an integer , gives each node at most children while retaining completeness and array storage. With zero-based indexing,
and the possible children of occupy indices , limited by the array length. Some course tasks call the same structure a k-ary heap.
The height is
.
sift_up compares with one parent per level, so insertion
and decrease-key use
comparisons. A downward step must inspect up to
children to identify the smallest, giving extract_min
comparisons in the comparison model.
Increasing therefore trades fewer levels for more work per downward level. A flatter layout can also improve locality, while a very large branching factor may waste comparisons. Report both comparisons and elapsed time when evaluating a d-ary heap: hardware behaviour cannot be inferred from asymptotic height alone.
Repeated insertion constructs a heap in , but it does unnecessary upward work. Leaves already satisfy heap order. Starting at the last internal node and sifting each internal node downward makes every processed subtree a heap:
build_heap(A, d):
if len(A) < 2: return
for i = floor((len(A) - 2) / d) down to 0:
sift_down(A, i, d)
Invariant. Just before index
is processed, all subtrees rooted at indices greater than
are heaps. The children of
have larger indices, so after sift_down the subtree at
is also a heap. At termination the root’s subtree is the whole heap.
The operation is linear because most nodes are near the leaves. For a binary heap, at most about nodes have height , so
The same geometric argument gives for every fixed , even after counting up to child inspections per downward level. Multiplying calls by the worst cost of one call would give a valid but unnecessarily loose bound.
Binary and d-ary heaps keep all items in one complete tree, which is excellent for locality but makes a general meld expensive. Meldable heaps instead use a forest whose trees can be combined structurally. Binomial heaps make that structure explicit; Fibonacci heaps later postpone some of the same combining work.
A binomial tree is defined recursively. is a single node, and is formed by linking two copies of , making one root a child of the other. Consequently, has nodes, height , and root degree . Its root’s children are roots of .
A binomial heap is a forest of heap-ordered binomial trees with at most one tree of each degree. Roots are stored in increasing degree order. Tree is present exactly when bit of the heap size is 1, so the forest is a structural binary representation of .
To link two degree- trees, compare their roots and make the larger-key root a child of the smaller-key root. Heap order is preserved, and the result is a .
To meld two heaps, first merge their root lists by degree. Equal-degree trees are linked, possibly creating another collision at the next degree. This is carry propagation in binary addition.
There are
possible degrees, so meld takes
.
Insertion is a meld with a one-node
.
To extract a minimum, scan the roots, remove the minimum-root tree,
reverse its descending-degree child list into an increasing-degree heap,
and meld it back. Decrease-key swaps a decreased item upward through
parent links until heap order is restored. These operations take
;
minimum is
unless a maintained minimum pointer makes it
.
Worst-case analysis prices one operation in isolation. Amortised analysis bounds the total cost of every sequence of operations; it is not an average over random inputs.
Let be the actual cost of operation , and let a nonnegative potential measure stored work after it. Define
The potential changes telescope:
If and potentials stay nonnegative, total actual cost is at most total amortised cost.
For a binary counter, let the cost of increment be the number of flipped bits and choose number of 1-bits. If an increment clears trailing ones and sets one zero, its actual cost is and its potential change is . Its amortised cost is therefore 2, even though an individual increment can flip bits.
The same carry idea explains lazy binomial heaps: insertion may append a in constant actual time, leaving extra roots as potential. A later consolidation pays for links by reducing the number of roots.
A Fibonacci heap pushes this laziness further. It stores a heap-ordered forest, a circular root list, and a pointer to a minimum root.
insert adds a singleton root.meld concatenates two root lists and keeps the smaller
minimum pointer.extract_min removes the minimum root, promotes its
children to roots, and consolidates roots of equal degree.decrease_key cuts a node whose new key violates parent
order and may perform cascading cuts up the ancestor
chain.A non-root node is marked after it loses its first child. If it loses another, it is cut; this may expose a marked parent and continue the cascade. With roots and marked nodes, the standard potential is
Suppose one decrease_key cuts
nodes. The first cut creates a root and clears that node’s mark if it
had one. Every later cut removes a marked node from its parent, creates
another root, and clears the mark; the cascade may finish by marking one
previously unmarked ancestor. Therefore
and
,
so
This potential change pays for all but constant amortised work in the -cost cascade. Thus insertion, meld, minimum, and decrease-key have amortised cost.
Extraction must consolidate degrees. Why are there only degrees after arbitrary cuts? Order a degree- node’s children by when they were linked to it. Its -th child had degree at least when linked and can lose at most one child while remaining below its parent, so its current degree is at least .
If is the minimum possible size of a degree- subtree, then , , and
where , , and .
Thus a degree- node has descendants, where , so the maximum degree is .
An extract_min may begin with many roots. Its actual
work is
:
process the root list, promote at most
children, and consolidate. Afterwards at most one root of each degree
remains, so
.
Promoted children become unmarked roots, hence
,
and
The term pays for scanning a long root list, leaving amortised work.
These are theoretical improvements for workloads with many decrease-key operations. Pointer-rich layouts, memory allocation, and larger constants often make binary, d-ary, or pairing heaps faster in real programs.
A van Emde Boas (vEB) structure changes the model: keys must be integers in a known universe . For the clean recursive description, round up so repeated square roots divide the key into equally sized high and low parts.
The structure stores minimum and maximum keys, divides the universe into clusters of size , and recursively records which clusters are nonempty in a summary structure. Successor, predecessor, membership, insertion, and deletion recurse into a universe of size :
The classic eager representation uses space, so it is attractive only when the integer universe and density justify it. Sparse and practical variants require additional ideas. The bound is not a comparison-based priority-queue bound; it relies on word operations and bounded integer keys.
Insert keys 7, 3, 9, 1 into a binary min-heap:
[7].[3, 7].[3, 7, 9].[3, 7, 9, 1]. Swap with 7, then with 3:
[1, 3, 9, 7].Now extract the minimum. Save 1, move the last item 7 to the root,
and shrink the array to [7, 3, 9]. Of children 3 and 9,
choose 3 and swap, producing [3, 7, 9]. Shape and heap
order are restored. Notice that 7 and 9 need not be sorted relative to
one another.
| Structure | Minimum | Insert | Extract min | Decrease key | Meld |
|---|---|---|---|---|---|
| Binary heap | by rebuild | ||||
| d-ary heap | by rebuild | ||||
| Binomial heap | , or tracked | ||||
| Fibonacci heap, amortised | |||||
| van Emde Boas | stored | model-dependent | not primary operation |
The table hides important assumptions: Fibonacci bounds are amortised, vEB bounds use bounded integers, and decrease-key presumes a direct handle to the item.
extract_min and
decrease_key; their bounds depend on the chosen heap.sift_down choose the minimum child?heapq
exposes an array-based binary min-heap and documents patterns for stable
priorities and lazy deletion. It has no handle-based
decrease_key or efficient meld, so graph algorithms
commonly insert a new pair and ignore stale entries when popped.BinaryHeap
is a standard-library binary max-heap; wrap keys in Reverse
for minimum-first order. It can move every item from another heap with
append, but the API promises no efficient structural meld
and provides no handles for arbitrary decrease-key.PriorityQueue
is an unbounded priority queue with logarithmic insertion and removal at
the head. Its iterator is not sorted, and changing fields used by the
comparator while an item remains queued breaks the intended
ordering.heapq.heapify(data) against inserting every item with
heappush, then pop everything to verify that both outputs
are sorted and plot time divided by
.sift_down for
.
On identical heaps, count key comparisons and elapsed time for a full
sequence of extractions; explain why the
with the fewest levels need not perform the fewest comparisons.