Pairing heap

Source: Wikipedia, the free encyclopedia.

A pairing heap is a type of

amortized performance, introduced by Michael Fredman, Robert Sedgewick, Daniel Sleator, and Robert Tarjan in 1986.[1]
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,[2]
and support the following operations (assuming a min-heap):

  • find-min: simply return the top element of the heap.
  • meld: compare the two root elements, the smaller remains the root of the result, the larger element and its subtree is appended as a child of this root.
  • insert: create a new heap for the inserted element and meld into the original heap.
  • decrease-key (optional): remove the subtree rooted at the key to be decreased, replace the key with a smaller key, then meld the result back into the heap.
  • delete-min: remove the root and do repeated melds of its subtrees until one tree remains. Various merging strategies are employed.

The analysis of pairing heaps' time complexity was initially inspired by that of splay trees.[1] The amortized time per delete-min is O(log n), and the operations find-min, meld, and insert run in O(1) time.[3]

When a decrease-key operation is added as well, determining the precise asymptotic running time of pairing heaps has turned out to be difficult. Initially, the time complexity of this operation was conjectured on empirical grounds to be O(1),[4] but Fredman proved that the amortized time per decrease-key is at least for some sequences of operations.[5] Using a different amortization argument, Pettie then proved that insert, meld, and decrease-key all run in amortized time, which is .[6] Elmasry later introduced elaborations of pairing heaps (lazy, consolidate) for which decrease-key runs in amortized time and other operations have optimal amortized bounds,[7][8] but no tight bound is known for the original data structure.[3][6]

Although the asymptotic performance of pairing heaps is worse than other priority queue algorithms such as Fibonacci heaps, which perform decrease-key in amortized time, the performance in practice is excellent. Jones[9] and Larkin, Sen, and Tarjan[10] conducted experiments on pairing heaps and other heap data structures. They concluded that d-ary heaps such as binary heaps are faster than all other heap implementations when the decrease-key operation is not needed (and hence there is no need to externally track the location of nodes in the heap), but that when decrease-key is needed pairing heaps are often faster than d-ary heaps and almost always faster than other pointer-based heaps, including data structures like Fibonacci heaps that are theoretically more efficient. Chen et al.[11] examined priority queues specifically for use with Dijkstra's algorithm and concluded that in normal cases using a d-ary heap without decrease-key (instead duplicating nodes on the heap and ignoring redundant instances) resulted in better performance, despite the inferior theoretical performance guarantees.

Structure

A pairing heap is either an empty heap, or a pairing tree consisting of a root element and a possibly empty list of pairing trees. The heap ordering property requires that parent of any node is no greater than the node itself. The following description assumes a purely functional heap that does not support the decrease-key operation.

type PairingTree[Elem] = Heap(elem: Elem, subheaps: List[PairingTree[Elem]])
type PairingHeap[Elem] = Empty | PairingTree[Elem]

A pointer-based implementation for

doubly-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). It can also be viewed as a variant of a Left-child right-sibling binary tree with an additional pointer to a node's parent (which represents its previous sibling or actual parent for the leftmost sibling). 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.[1]

Operations

Meld

Melding 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 meld(heap1, heap2: PairingHeap[Elem]) -> PairingHeap[Elem]
    if heap1 is Empty
        return heap2
    elsif heap2 is 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 meld the heap with a new heap containing just this element and an empty list of subheaps:

function insert(elem: Elem, heap: PairingHeap[Elem]) -> PairingHeap[Elem]
    return meld(Heap(elem, []), heap)

Find-min

The function find-min simply returns the root element of the heap:

function find-min(heap: PairingHeap[Elem]) -> Elem
    if heap is Empty
        error
    else
        return heap.elem

Delete-min

The only non-trivial fundamental operation is the deletion of the minimum element from the heap. This requires performing repeated melds of its children until only one tree remains. The standard strategy first melds the subheaps in pairs (this is the step that gave this data structure its name) from left to right and then melds the resulting list of heaps from right to left:

function delete-min(heap: PairingHeap[Elem]) -> PairingHeap[Elem]
    if heap is Empty
        error
    else
        return merge-pairs(heap.subheaps)

This uses the auxiliary function merge-pairs:

function merge-pairs(list: List[PairingTree[Elem]]) -> PairingHeap[Elem]
    if length(list) == 0
        return Empty
    elsif length(list) == 1
        return list[0]
    else
        return meld(meld(list[0], list[1]), merge-pairs(list[2..]))

That this does indeed implement the described two-pass left-to-right then right-to-left merging strategy can be seen from this reduction:

   merge-pairs([H1, H2, H3, H4, H5, H6, H7])
=> meld(meld(H1, H2), merge-pairs([H3, H4, H5, H6, H7]))
     # meld H1 and H2 to H12, then the rest of the list
=> meld(H12, meld(meld(H3, H4), merge-pairs([H5, H6, H7])))
     # meld H3 and H4 to H34, then the rest of the list
=> meld(H12, meld(H34, meld(meld(H5, H6), merge-pairs([H7]))))
     # meld H5 and H6 to H56, then the rest of the list
=> meld(H12, meld(H34, meld(H56, H7)))
     # switch direction, meld the last two resulting heaps, giving H567
=> meld(H12, meld(H34, H567))
     # meld the last two resulting heaps, giving H34567
=> meld(H12, H34567) 
     # finally, meld the first pair with the result of merging the rest
=> H1234567

Summary of running times

Here are time complexities[12] of various heap data structures. Function names assume a min-heap. For the meaning of "O(f)" and "Θ(f)" see Big O notation.

Operation find-min delete-min insert decrease-key meld
Binary[12] Θ(1) Θ(log n) O(log n) O(log n) Θ(n)
Leftist Θ(1) Θ(log n) Θ(log n) O(log n) Θ(log n)
Binomial[12][13] Θ(1) Θ(log n) Θ(1)[a] Θ(log n) O(log n)
Skew binomial[14] Θ(1) Θ(log n) Θ(1) Θ(log n) O(log n)[b]
Pairing[3] Θ(1) O(log n)[a] Θ(1) o(log n)[a][c] Θ(1)
Rank-pairing[17] Θ(1) O(log n)[a] Θ(1) Θ(1)[a] Θ(1)
Fibonacci[12][18] Θ(1) O(log n)[a] Θ(1) Θ(1)[a] Θ(1)
Strict Fibonacci[19] Θ(1) O(log n) Θ(1) Θ(1) Θ(1)
Brodal[20][d] Θ(1) O(log n) Θ(1) Θ(1) Θ(1)
2–3 heap[22] Θ(1) O(log n)[a] Θ(1)[a] Θ(1) O(log n)
  1. ^ a b c d e f g h i Amortized time.
  2. ^ Brodal and Okasaki describe a technique to reduce the worst-case complexity of meld to Θ(1); this technique applies to any heap datastructure that has insert in Θ(1) and find-min, delete-min, meld in O(log n).
  3. ^ Lower bound of [15] upper bound of [16]
  4. ^ Brodal and Okasaki later describe a persistent variant with the same bounds except for decrease-key, which is not supported. Heaps with n elements can be constructed bottom-up in O(n).[21]

References

  1. ^
    S2CID 23664143
    .
  2. ^ Mehlhorn, Kurt; Sanders, Peter (2008). Algorithms and Data Structures: The Basic Toolbox (PDF). Springer. p. 231.
  3. ^
  4. ^
    S2CID 17811811
  5. S2CID 16115266. Archived from the original
    (PDF) on 2011-07-21. Retrieved 2011-05-03.
  6. ^
  7. .
  8. .
  9. ^ Chen, Mo; Chowdhury, Rezaul Alam; Ramachandran, Vijaya; Roche, David Lan; Tong, Lingling (12 October 2007). Priority Queues and Dijkstra's Algorithm (PDF) (Technical report). University of Texas. TR-07-54.{{cite tech report}}: CS1 maint: date and year (link)
  10. ^ .
  11. ^ "Binomial Heap | Brilliant Math & Science Wiki". brilliant.org. Retrieved 2019-09-30.
  12. .
  13. .
  14. .
  15. .
  16. .
  17. ^ Brodal, Gerth S. (1996), "Worst-Case Efficient Priority Queues" (PDF), Proc. 7th Annual ACM-SIAM Symposium on Discrete Algorithms, pp. 52–58
  18. .
  19. ^ Takaoka, Tadao (1999), Theory of 2–3 Heaps (PDF), p. 12

External links