This is my first attempt at implementing a heap data structure and I've decided to cover the methods Peek
, Pop
, Push
, and Replace
. Think appear to work correctly but I admittedly had a lot of trouble figuring out the "ideal" way to implement each function as the reference implementations I found were all over the place.
Any feedback, especially if it addresses concerns that aren't related to formatting/naming, is most appreciated.
public class BinaryHeap<T>
{
private readonly Func<T, T, bool> m_comparer;
private readonly IList<T> m_values;
private int m_nextIndex;
public int Capacity => m_values.Count;
public int Count => m_nextIndex;
public BinaryHeap(IList<T> values, Func<T, T, bool> comparer) {
m_comparer = comparer;
m_values = values;
Grow(checked((int)(Operations.NextPowerOf2(Capacity) - 1)));
Rebuild();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Peek() {
return m_values[0];
}
public T Pop() {
if (TryPop(out T value)) {
return value;
}
else {
throw new InvalidOperationException(message: "heap is empty");
}
}
public int Push(T value) {
var offset = Count;
if (!(offset < Capacity)) {
Grow(checked((int)(Operations.NextPowerOf2(Capacity + 1) - 1)));
}
while (0 < offset) {
var parent = ((offset - 1) >> 1);
if (m_comparer(m_values[parent], value)) {
break;
}
m_values[offset] = m_values[parent];
offset = parent;
}
m_values[offset] = value;
return m_nextIndex++;
}
public T Replace(T newValue) {
if (TryReplace(newValue, out T oldValue)) {
return oldValue;
}
else {
throw new InvalidOperationException(message: "heap is empty");
}
}
public bool TryReplace(T newValue, out T oldValue) {
if (0 < Count) {
oldValue = Peek();
m_values[0] = newValue;
Heapify(0);
return true;
}
else {
oldValue = default;
return false;
}
}
public bool TryPop(out T value) {
if (0 < Count) {
value = Peek();
m_values[0] = m_values[--m_nextIndex];
m_values[m_nextIndex] = default;
Heapify(0);
return true;
}
else {
value = default;
return false;
}
}
private void Grow(int maxCapacity) {
var currentCapacity = Capacity;
for (var i = currentCapacity; (i < maxCapacity); i++) {
m_values.Add(default);
}
m_nextIndex = currentCapacity;
}
private void Heapify(int offset) {
var count = Count;
while (offset < count) {
var left = ((offset << 1) + 1);
var right = (left + 1);
var parent = offset;
if ((left < count) && m_comparer(m_values[left], m_values[parent])) {
parent = left;
}
if ((right < count) && m_comparer(m_values[right], m_values[parent])) {
parent = right;
}
if (offset == parent) {
return;
}
Swap(ref offset, ref parent);
}
}
private void Rebuild() {
for (var i = ((Count - 1) >> 1); (-1 < i); i--) {
Heapify(i);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Swap(ref int x, ref int y) {
var temp = m_values[x];
m_values[x] = m_values[y];
m_values[y] = temp;
x = y;
}
}
As requested, here are the missing method(s):
public static class Operations
{
public static long NextPowerOf2(long value) {
return ((long)NextPowerOf2((ulong)value));
}
public static ulong NextPowerOf2(ulong value) {
return (FoldBits(value) + 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong FoldBits(ulong value) {
value |= (value >> 1);
value |= (value >> 2);
value |= (value >> 4);
value |= (value >> 8);
value |= (value >> 16);
return (value | (value >> 32));
}
}