Ответ 1
Поведение можно воспроизвести с помощью вектора инициализации [0, 1, 2, 4, 5, 3]
. Результат:
[0, 1, 2, 4, 3, 5]
(мы видим, что 3 неправильно помещено)
Алгоритм Push
правильный. Он создает минимальную кучу простым способом:
- Начните с нижнего правого
- Если значение больше родительского node, тогда вставьте его и верните
- В противном случае поставьте вместо этого родителя в нижнем правом положении, затем попробуйте вставить значение в родительское место (и продолжайте заменять дерево до тех пор, пока не будет найдено нужное место).
Полученное дерево:
0
/ \
/ \
1 2
/ \ /
4 5 3
Проблема связана с методом Pop
. Он начинается с рассмотрения верхнего node в качестве "пробела" для заполнения (поскольку мы выталкивали его):
*
/ \
/ \
1 2
/ \ /
4 5 3
Чтобы заполнить его, он ищет младшего немедленного ребенка (в данном случае: 1). Затем он перемещает значение вверх, чтобы заполнить пробел (и теперь ребенок стал новым разрывом):
1
/ \
/ \
* 2
/ \ /
4 5 3
Затем он делает то же самое с новым зазором, поэтому зазор снова опускается:
1
/ \
/ \
4 2
/ \ /
* 5 3
Когда зазор достиг дна, алгоритм... берет нижнее правое значение дерева и использует его для заполнения пробела:
1
/ \
/ \
4 2
/ \ /
3 5 *
Теперь, когда зазор находится в нижней правой части node, он уменьшает _count
, чтобы удалить пробел из дерева:
1
/ \
/ \
4 2
/ \
3 5
И мы закончим с... Сломанной кучей.
Честно говоря, я не понимаю, что пытался сделать автор, поэтому я не могу исправить существующий код. В лучшем случае я могу поменять его рабочей версией (бесстыдно скопирован из Wikipedia):
internal void Pop2()
{
if (_count > 0)
{
_count--;
_heap[0] = _heap[_count];
Heapify(0);
}
}
internal void Heapify(int i)
{
int left = (2 * i) + 1;
int right = left + 1;
int smallest = i;
if (left <= _count && _comparer.Compare(_heap[left], _heap[smallest]) < 0)
{
smallest = left;
}
if (right <= _count && _comparer.Compare(_heap[right], _heap[smallest]) < 0)
{
smallest = right;
}
if (smallest != i)
{
var pivot = _heap[i];
_heap[i] = _heap[smallest];
_heap[smallest] = pivot;
Heapify(smallest);
}
}
Основная проблема с этим кодом - это рекурсивная реализация, которая будет ломаться, если количество элементов слишком велико. Я настоятельно рекомендую использовать оптимизированную библиотеку третьей стороны.
Изменить: Я думаю, что узнал, чего не хватает. После взятия нижнего правого node автор просто забыл перебалансировать кучу:
internal void Pop()
{
Debug.Assert(_count != 0);
if (_count > 1)
{
// Loop invariants:
//
// 1. parent is the index of a gap in the logical tree
// 2. leftChild is
// (a) the index of parent left child if it has one, or
// (b) a value >= _count if parent is a leaf node
//
int parent = 0;
int leftChild = HeapLeftChild(parent);
while (leftChild < _count)
{
int rightChild = HeapRightFromLeft(leftChild);
int bestChild =
(rightChild < _count && _comparer.Compare(_heap[rightChild], _heap[leftChild]) < 0) ?
rightChild : leftChild;
// Promote bestChild to fill the gap left by parent.
_heap[parent] = _heap[bestChild];
// Restore invariants, i.e., let parent point to the gap.
parent = bestChild;
leftChild = HeapLeftChild(parent);
}
// Fill the last gap by moving the last (i.e., bottom-rightmost) node.
_heap[parent] = _heap[_count - 1];
// FIX: Rebalance the heap
int index = parent;
var value = _heap[parent];
while (index > 0)
{
int parentIndex = HeapParent(index);
if (_comparer.Compare(value, _heap[parentIndex]) < 0)
{
// value is a better match than the parent node so exchange
// places to preserve the "heap" property.
var pivot = _heap[index];
_heap[index] = _heap[parentIndex];
_heap[parentIndex] = pivot;
index = parentIndex;
}
else
{
// Heap is balanced
break;
}
}
}
_count--;
}