A pairing heap is a type of heap data structure with relatively simple implementation and excellent practical amortized performance, introduced by Michael Fredman, Robert Sedgewick, Daniel Sleator, and Robert Tarjan in 1986. Pairing heaps are heap-ordered multiway tree structures, and can be considered simplified Fibonacci heaps. They are considered a "robust choice" for implementing such algorithms as Prim's MST algorithm, and support the following operations (assuming a min-heap):
Contents
The analysis of pairing heaps' time complexity was initially inspired by that of splay trees. The amortized time per delete-min is O(log n), and the operations find-min, merge, and insert run in O(1) amortized time.
Determining the precise asymptotic running time of pairing heaps when a decrease-key operation is needed has turned out to be difficult. Initially, the time complexity of this operation was conjectured on empirical grounds to be O(1), but Fredman proved that the amortized time per decrease-key is at least
Although this is worse than other priority queue algorithms such as Fibonacci heaps, which perform decrease-key in
Structure
A pairing heap is either an empty heap, or a pair consisting of a root element and a possibly empty list of pairing heaps. The heap ordering property requires that all the root elements of the subheaps in the list are not smaller than the root element of the heap. The following description assumes a purely functional heap that does not support the decrease-key operation.
type PairingHeap[Elem] = Empty | Heap(elem: Elem, subheaps: List[PairingHeap[Elem]])A pointer-based implementation for RAM machines, supporting decrease-key, can be achieved using three pointers per node, by representing the children of a node by a singly-linked list: a pointer to the node's first child, one to its next sibling, and one to its previous sibling (or, for the leftmost sibling, to its parent). Alternatively, the previous-pointer can be omitted by letting the last child point back to the parent, if a single boolean flag is added to indicate "end of list". This achieves a more compact structure at the expense of a constant overhead factor per operation.
find-min
The function find-min simply returns the root element of the heap:
function find-min(heap) if heap == Empty error else return heap.elemmerge
Merging with an empty heap returns the other heap, otherwise a new heap is returned that has the minimum of the two root elements as its root element and just adds the heap with the larger root to the list of subheaps:
function merge(heap1, heap2) if heap1 == Empty return heap2 elsif heap2 == Empty return heap1 elsif heap1.elem < heap2.elem return Heap(heap1.elem, heap2 :: heap1.subheaps) else return Heap(heap2.elem, heap1 :: heap2.subheaps)insert
The easiest way to insert an element into a heap is to merge the heap with a new heap containing just this element and an empty list of subheaps:
function insert(elem, heap) return merge(Heap(elem, []), heap)delete-min
The only non-trivial fundamental operation is the deletion of the minimum element from the heap. The standard strategy first merges the subheaps in pairs (this is the step that gave this datastructure its name) from left to right and then merges the resulting list of heaps from right to left:
function delete-min(heap) if heap == Empty error else return merge-pairs(heap.subheaps)
This uses the auxiliary function merge-pairs:
That this does indeed implement the described two-pass left-to-right then right-to-left merging strategy can be seen from this reduction:
Summary of running times
In the following time complexities O(f) is an asymptotic upper bound and Θ(f) is an asymptotically tight bound (see Big O notation). Function names assume a min-heap.