1 完全二叉堆的结构特点
完全二叉堆通常也简称为堆,是一种建立在完全二叉树基础上的数据结构(注意不要和内存中的堆弄混),同时,构建堆的完全二叉树还需要满足一个特点:任何叶子节点都不大于(小顶堆)/不小于(大顶堆)其父节点。
2 优先级队列与完全二叉堆
通常,优先级队列是使用完全二叉堆实现的。那为什么要选择堆而不是其它数据结构实现优先级队列呢?因为堆结构在实现优先级队列这件事情上很有优势:
- 从算法复杂度的角度,入队和出队操作的复杂度都是O(n),或许BBST(平衡二叉搜索树)也能做到这一点,不过获取队首元素时,堆只需O(1)的复杂度。
- 从内存和高速缓存的角度,由于完全二叉树的结构特点,可以用向量(数组)来实现堆,内存上非常紧凑,既节省了指向孩子节点的指针,也更有利于Cache机制发挥作用,空间和时间上都非常有优势。
3 实现
3.1 用向量结构来实现
接着上文,如何用数组来存放一个完全二叉树呢?且看下图:
光有用数组保存的完全二叉树是不够的,我们还要维护这棵树使它具有堆的特点——任何叶子节点都不大于(小顶堆)/不小于(大顶堆)其父节点。先考虑一个问题,堆的结构特点在什么时候会被破坏?当然是删除堆顶元素或向堆添加新元素的时候,此时就要用到上滤和下滤操作。
3.2 上滤
上滤用于向堆添加新元素的时候,以小顶堆为例,当新增的元素小于其父节点时,就将该元素与其父节点交换位置,直到该元素不再小于其父节点:
3.3 下滤
下滤用于删除堆顶元素的时候,以小顶堆为例,当删除堆顶元素时,首先将堆顶元素和堆的最后一个元素(在数组中下标最大的元素)交换,然后将堆的大小减1(移除换到最后的堆顶元素)。联系堆的结构特点,通常被换到堆顶的元素是大于其孩子节点的,这就破坏了堆的结构特点。此时选择该节点的两个孩子中较小的那个,并交换位置,如果交换后该节点还是大于孩子节点,那么就继续上述流程,直到堆的结构特性得到恢复:
3.4 程序
这里给出一个小顶堆的具体实现,代码仅仅是示例,通常不会将堆写死为小顶或大顶,而是根据调用者提供的比较函数来进行元素的比较。关于通用的堆的实现,读者可自行尝试,原理都差不多:
class MinHeap {
private:
vector<int> datas;
void up(int idx);
void down(int idx);
public:
MinHeap() = default;
~MinHeap() = default;
bool empty() const { return datas.empty(); }
const int &top() const;
void pop();
void push(const int &data);
};
/* 上滤操作 */
void MinHeap::up(int idx) {
while ((idx > 0) && (datas[idx] < datas[(idx - 1) / 2])) {
swap(datas[idx], datas[(idx - 1) / 2]);
idx = (idx - 1) / 2;
}
}
/* 下滤操作 */
void MinHeap::down(int idx) {
int i = idx;
while ((2 * i + 2) < datas.size())
{
int min_index = (datas[2 * i + 1] < datas[2 * i + 2]) ? (2 * i + 1) : (2 * i + 2);
if (datas[i] <= datas[min_index])
break;
swap(datas[i], datas[min_index]);
i = min_index;
}
if (((2 * i + 1) < datas.size()) && (datas[i] > datas[2 * i + 1]))
swap(datas[i], datas[2 * i + 1]);
}
/* 获取队首元素 */
const int & MinHeap::top() const {
assert(!datas.empty());
return datas.front();
}
/* 将队首元素出队 */
void MinHeap::pop() {
assert(!datas.empty());
/* 首尾交换 */
swap(datas.front(), datas.back());
/* 移除当前尾部节点 */
datas.pop_back();
/* 对首部节点进行下滤 */
down(0);
}
/* 将新元素入队 */
void MinHeap::push(const int &data) {
int idx = datas.size();
datas.emplace_back(data);
up(idx);
}