In this post I am sharing another code Kata: an implementation of a binary heap. Once we have the heap implemented, we will easily deduce a heap sort and a priority queue based on it. It takes about 100 lines of C# code.
Binary Heap
The heap tree is “threaded”: the tree nodes are stored in an array list provided in the constructor (line 6). For each element at index i: its left child is at index 2i+1, its right child is at 2i+2 and its parent is at (i−1)/2. These relations are respectively implemented in RightChild (line 80), LeftChild (line 82) and Parent (line 78) methods.
If the provided list is not empty, it will be “heapified”. This is done using the Heapify method (line 40) and is similar to STL’s make_heap operation in C++.
New elements can be inserted to the heap using the Insert method (line 32). A new element will be first put at the end of the tree, then pulled up until the tree satisfies the heap property again. This operation is implemented in the HeapUp method (line 48).
The root element of the heap can be removed from the tree using the PopRoot method (line 16). The last element of the tree is then put at the root and pushed down until the tree satisfies the heap invariant. This is implemented using the HeapDown method (line 60).
public class Heap<T>
{
private readonly IList<T> _list;
private readonly IComparer<T> _comparer;
public Heap(IList<T> list, int count, IComparer<T> comparer)
{
_comparer = comparer;
_list = list;
Count = count;
Heapify();
}
public int Count { get; private set; }
public T PopRoot()
{
if (Count == 0) throw new InvalidOperationException("Empty heap.");
var root = _list[0];
SwapCells(0, Count - 1);
Count--;
HeapDown(0);
return root;
}
public T PeekRoot()
{
if (Count == 0) throw new InvalidOperationException("Empty heap.");
return _list[0];
}
public void Insert(T e)
{
if (Count >= _list.Count) _list.Add(e);
else _list[Count] = e;
Count++;
HeapUp(Count - 1);
}
private void Heapify()
{
for (int i = Parent(Count - 1); i >= 0; i--)
{
HeapDown(i);
}
}
private void HeapUp(int i)
{
T elt = _list[i];
while (true)
{
int parent = Parent(i);
if (parent < 0 || _comparer.Compare(_list[parent], elt) > 0) break;
SwapCells(i, parent);
i = parent;
}
}
private void HeapDown(int i)
{
while (true)
{
int lchild = LeftChild(i);
if (lchild < 0) break;
int rchild = RightChild(i);
int child = rchild < 0
? lchild
: _comparer.Compare(_list[lchild], _list[rchild]) > 0 ? lchild : rchild;
if (_comparer.Compare(_list[child], _list[i]) < 0) break;
SwapCells(i, child);
i = child;
}
}
private int Parent(int i) { return i <= 0 ? -1 : SafeIndex((i - 1) / 2); }
private int RightChild(int i) { return SafeIndex(2 * i + 2); }
private int LeftChild(int i) { return SafeIndex(2 * i + 1); }
private int SafeIndex(int i) { return i < Count ? i : -1; }
private void SwapCells(int i, int j)
{
T temp = _list[i];
_list[i] = _list[j];
_list[j] = temp;
}
}
Quick Complexity Analysis
The complexity of HeapUp and HeapDown are bound by the depth of the (complete) tree. Hence, they have both a O(log(N)) complexity (where N is the number of elements in the heap). As a consequence, Insert and PopRoot have both the same complexity: O(log(N)).
PeekRoot doesn’t imply any structural changes and has a O(1) complexity.
The Heapify method has O(N) complexity; a detailed analysis is presented here. Therefore, constructing a heap with a non empty list of size count will cost O(count).
Heap Sort
The heap sort is quite straightforward given the binary heap above. We just build a heap with the input array, this is O(N), and then pop the root element from the heap until it is empty, this is N times O(log(N)). The presented heap sort here is done in-place, with O(1) space complexity and with O(N log(N)) worst case time complexity.
public class HeapSort<T>
{
public static void Sort(IList<T> list, IComparer<T> comparer)
{
var heap = new Heap<T>(list, list.Count, comparer);
while (heap.Count > 0)
{
heap.PopRoot();
}
}
}
Priority Queue
In fact the heap is a priority queue
. We just need a wrapper (delegator) object to create the heap and the heap’s list. The code is self-explanatory.
public class PriorityQueue<T>
{
private readonly Heap<T> _heap;
public PriorityQueue(IComparer<T> comparer)
{
_heap = new Heap<T>(new List<T>(), 0, comparer);
}
public int Size { get { return _heap.Count; } }
public T Top() { return _heap.PeekRoot(); }
public void Push(T e) { _heap.Insert(e); }
public T Pop() { return _heap.PopRoot(); }
}
horia toma
January 8, 2011 at 10:10 pmnice, but I didn’t get the exact meaning of Count. you’re supposed to operate on a given first Count elements of the passed list?
you should add a small check on Parent method to see if you’re not dealing with the root.
Chaker Nakhli
April 23, 2012 at 12:38 pm@Horia Toma : Parent method fixed as you suggested. Article updated. Thanks!
Chaker
January 8, 2011 at 10:34 pmExactly, the heap operates only on the first Count elements (same as make_heap in C++ will heapify [start, end) interval where start and end are random access iterators). Note that each time you PopRoot, the last element is swapped with the root and Count is decreased. The max element is actually swapped at position Count. If you continue deleting the max element from the heap and swapping it at Count until Count==0, the underlying array will be sorted. This is what does the heap sort algorithm.
Thank you for spotting the Parent method bug! I’ll try to fix it asap. Thank you for the review
Pingback: Tweets that mention Binary Heap, Heap Sort and Priority Queue - Chaker Nakhli's Blog -- Topsy.com
InnopiplyTody
March 4, 2011 at 10:48 pmproc ne:)
Pingback: C# Implementations of Binary Heaps – Matej++