堆¶
堆的定义¶
「堆 heap」是一种满足特定条件的完全二叉树,主要可分为两种类型,如下图所示。
「大顶堆 max heap」:任意节点的值 \(\ge\) 其子节点的值。
「小顶堆 min heap」:任意节点的值 \(\le\) 其子节点的值。
堆的实现¶
1.堆的储存和表示¶
“二叉树”章节讲过,完全二叉树非常适合用数组来表示。由于堆正是一种完全二叉树,因此我们将采用数组来存储堆。
当使用数组表示二叉树时,元素代表节点值,索引代表节点在二叉树中的位置。节点指针通过索引映射公式来实现。
如图 8-2 所示,给定索引 \(i\) ,其左子节点索引为 \(2i+1\) ,右子节点索引为\(2i+2\) ,父节点索引为 \(\frac{i-1}{2}\)(向下整除)。当索引越界时,表示空节点或节点不存在。
/* 获取左子节点索引 */
int left(MaxHeap *maxHeap, int i) {
return 2 * i + 1;
}
/* 获取右子节点索引 */
int right(MaxHeap *maxHeap, int i) {
return 2 * i + 2;
}
/* 获取父节点索引 */
int parent(MaxHeap *maxHeap, int i) {
return (i - 1) / 2;
}
2.访问堆顶元素¶
3.元素入堆¶
给定元素 val
,我们首先将其添加到堆底。添加之后,由于 `val`` 可能大于堆中其他元素,堆的成立条件可能已被破坏,因此需要修复从插入节点到根节点的路径上的各个节点,这个操作被称为「堆化 heapify」。
考虑从入堆节点开始,从底至顶执行堆化 。如下图所示,我们比较插入节点与其父节点的值,如果插入节点更大,则将它们交换。然后继续执行此操作,从底至顶修复堆中的各个节点,直至越过根节点或遇到无须交换的节点时结束。
/* 元素入堆 */
void push(MaxHeap *maxHeap, int val) {
// 默认情况下,不应该添加这么多节点
if (maxHeap->size == MAX_SIZE) {
printf("heap is full!");
return;
}
// 添加节点
maxHeap->data[maxHeap->size] = val;
maxHeap->size++;
// 从底至顶堆化
siftUp(maxHeap, maxHeap->size - 1);
}
/* 从节点 i 开始,从底至顶堆化 */
void siftUp(MaxHeap *maxHeap, int i) {
while (true) {
// 获取节点 i 的父节点
int p = parent(maxHeap, i);
// 当“越过根节点”或“节点无须修复”时,结束堆化
if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) {
break;
}
// 交换两节点
swap(maxHeap, i, p);
// 循环向上堆化
i = p;
}
}
时间复杂度:\(O(logn)\)
堆顶出堆¶
堆顶元素是二叉树的根节点,即列表首元素。如果我们直接从列表中删除首元素,那么二叉树中所有节点的索引都会发生变化,这将使得后续使用堆化进行修复变得困难。为了尽量减少元素索引的变动,我们采用以下操作步骤。
- 交换堆顶元素与堆底元素(交换根节点与最右叶节点)。
- 交换完成后,将堆底从列表中删除(注意,由于已经交换,因此实际上删除的是原来的堆顶元素)。
- 从根节点开始,从顶至底执行堆化。 “从顶至底堆化”的操作方向与“从底至顶堆化”相反,我们将根节点的值与其两个子节点的值进行比较,将最大的子节点与根节点交换。然后循环执行此操作,直到越过叶节点或遇到无须交换的节点时结束。
/* 元素出堆 */
int pop(MaxHeap *maxHeap) {
// 判空处理
if (isEmpty(maxHeap)) {
printf("heap is empty!");
return INT_MAX;
}
// 交换根节点与最右叶节点(交换首元素与尾元素)
swap(maxHeap, 0, size(maxHeap) - 1);
// 删除节点
int val = maxHeap->data[maxHeap->size - 1];
maxHeap->size--;
// 从顶至底堆化
siftDown(maxHeap, 0);
// 返回堆顶元素
return val;
}
/* 从节点 i 开始,从顶至底堆化 */
void siftDown(MaxHeap *maxHeap, int i) {
while (true) {
// 判断节点 i, l, r 中值最大的节点,记为 max
int l = left(maxHeap, i);
int r = right(maxHeap, i);
int max = i;
if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) {
max = l;
}
if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) {
max = r;
}
// 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出
if (max == i) {
break;
}
// 交换两节点
swap(maxHeap, i, max);
// 循环向下堆化
i = max;
}
}