(最大)堆的定义
堆总是一棵完全二叉树;且
最大堆总满足,堆中父节点的值总是大于等于其左右子节点的值;
最小堆总满足,堆中父节点的值总是小于等于其左右子节点的值。
(最大)堆的底层实现
1.数据存储
如下图所示,如果自顶向下,自左向右依次将完全二叉树中的每个节点标上序号(【注】此处从1开始),并使用数组存储(【注】数组中下标0对应的位置未放置元素),则可以发现规律:如果当前节点的序号为i
,则其父节点的序号为i/2
,其左孩子节点的序号为2*i
,其右孩子节点的序号为2*i+1
。
2.插入元素时,最大堆的维护(shiftUp)
如图所示,新插入的元素首先放在最后。维护的逻辑为:依次与其父节点比较大小,若大于父节点,则交换二者的值;直到某次比较发现小于等于父节点满足或已经为根节点。
对于图中给出的例子,待比较的节点,已使用绿色圆圈表示。
直接给出调整之后的结果图:
3.取出最大值时,最大堆的维护(shiftDown)
如图所示,当从最大堆中取出堆顶(即最大)元素后,维护的逻辑为:首先将最后一个元素放在堆顶位置,然后以堆顶为起点,执行shiftDown。即依次与其左右子节点中较大的一个较大小,若大于父节点,则交换二者的值;直到某次比较发现大于等于左右子节点满足或已经为叶子节点。
对于图中给出的例子,待比较的节点,已使用绿色圆圈表示。
直接给出调整之后的结果图:
最大堆的C++实现
#include <iostream>
#include <cassert>
#include <random>
template<typename Item>
class MaxHeap {
private:
Item* _data; // 维护一个指向数组的指针!
int _size; // 数组中,实际存在的【元素个数】
int _capacity; // 数组的 最大容积!
void shiftUp(int k) {
// 将k位置元素与其父节点元素比较,直到符合不大于的要求,或至根节点
while (k > 1 && _data[k] > _data[k / 2]) {
std::swap(_data[k], _data[k / 2]);
k /= 2;
}
}
void shiftDown(int k) {
//if (_size == 0) return;
//if (_size == 1) return;
while (2 * k <= _size) { // 【左节点存在】!不是叶节点,可能还需要交换
int j = 2 * k; // j表示 待交换的节点;此时表示左节点
if (j + 1 <= _size && _data[j + 1] > _data[j]) { // 【右节点存在】,且比左节点大!
j = j + 1; // 更新待交换节点
}
if (_data[k] < _data[j]) { // 当前节点比待交换节点小!
std::swap(_data[k], _data[j]);
k = j;
}
else break; // 符合父节点大于左右子节点
}
return;
}
public:
MaxHeap(int capacity) :_capacity(capacity) {
_data = new Item[capacity + 1]; // 从1开始保存元素
_size = 0; // 初始时,容积为capacity,实际元素个数为0.
}
~MaxHeap() {
delete[] _data;
}
int size() {
return _size;
}
bool isEmpty() {
return _size == 0;
}
void insert(Item item) {
// 0 1 2 3 ... _size ... _capacity
// - # # # ... #
assert(_size + 1 <= _capacity);
// 暂时实现固定容量大小的堆
_data[_size + 1] = item;
_size++;
shiftUp(_size);
}
Item extractMax() {
assert(_size >= 1); // [1, ... _size]保存元素
Item ret = _data[_size];
std::swap(_data[1], _data[_size]);
_size--;
shiftDown(1);
return ret;
}
void printMaxHeap() {
std::cout << "maxheap is: ";
for (int i = 1; i <= _size; ++i) {
std::cout << _data[i] << " ";
}
std::cout << std::endl;
}
};
int main() {
MaxHeap<int> maxheap = MaxHeap<int>(100);
std::uniform_int_distribution<unsigned> u(0, 100);
std::default_random_engine e;
// 生成0到100之间(包含)均匀分布的随机数
for (size_t i = 0; i < 10; ++i) {
maxheap.insert(u(e));
}
maxheap.printMaxHeap();
maxheap.extractMax();
maxheap.printMaxHeap();
maxheap.extractMax();
maxheap.printMaxHeap();
return 0;
}
程序的运行结果:
此时,堆是基于C++语言,实现(一种抽象数据类型ADT)的一整套算法,是一种数据结构。
ps. 抽象数据类型 VS 数据结构
最后类比一下vector的实现,体会一下堆: