最小堆特征
最小堆是一棵完全二叉树,其父节点的值,是左右子树的最小值。通常使用数组来实现:
- 树的根为Arr[0]
- 对于其他 i 节点,则有其他公式:
公式 | 返回值 |
---|---|
Arr[(i-1)/2] | 返回 i 节点的父节点 |
Arr[(2*i)+1] | 返回 i 节点的左孩子 |
Arr[(2*i)+2] | 返回 i 节点的右孩子 |
最小堆类定义
先看最小堆的class 定义声明,我们使用数组实现最小堆
class MinHeap
{
private:
int *harr; // pointer to array of elements in heap
int capacity; // maximum possible size of min heap
int heap_size; // Current number of elements in min heap
public:
MinHeap(int capacity);
int GetParent(int i) { return (i-1)/2; }
int GetLeftChild(int i) { return (2*i + 1); }
int GetRightChild(int i) { return (2*i + 2); }
int IsEmpty() {return heap_size > 0; }
int GetRoot() { return harr[0]; }
void PushBack(int k)
void Pop();
private:
void MinHeapify(int i);
// 交换第 i 和第 j 元素值
void Swap(int i, int j);
};
构造函数直接设定堆的最大存储空间:
MinHeap::MinHeap(int cap)
{
heap_size = 0;
capacity = cap;
harr = new int[capacity];
}
接下来我们主要实现堆的追加元素和弹出元素:
void MinHeap::Swap(int i, int j)
{
int temp = harr[i];
harr[i] = h[j];
h[j] = temp;
}
// 直接将追加元素添加到数组末尾,然后再做堆平衡
void MinHeap::PushBack(int k)
{
if (heap_size == capacity) {
cout << "Overflow: Could not insertKey"<< endl;
return;
}
heap_size++;
int i = heap_size - 1;
harr[i] = k;
// Fix the min heap property if it is violated
while (i != 0 && harr[parent(i)] > harr[i]) {
Swap(i, parent(i));
i = parent(i);
}
}
// 弹出根元素
void MinHeap::Pop() {
if (heap_size <= 0) {
return;
}
if (heap_size == 1) {
heap_size--;
return harr[0];
}
// 直接将树底元素作为新的根,然后找到根、左孩子、右孩子的最小元素,swap后,递归平衡下去
harr[0] = harr[heap_size-1];
heap_size--;
MinHeapify(0);
return;
}
// 递归平衡最小堆
// 假设i节点的左子树和右子树都是平衡的
void MinHeap::MinHeapify(int i)
{
int left = GetLeftChild(i);
int right = GetRightChild(i);
int smallest = i;
if (left < heap_size && harr[left] < harr[smallest])
smallest = left;
if (right < heap_size && harr[right] < harr[smallest])
smallest = right;
if (smallest != i) {
Swap(i, smallest);
MinHeapify(smallest);
}
}
最大堆的实现也是类似,只需要把其中父子间比较大小的操作反向即可。
上面的实现对于通常的最大堆,最小堆已经足够使用。但是如果还需要实现下沉(Sink)和上浮操作,可以如下实现: