617 字
3 分钟

二叉堆

2025-05-04
无标签
文章目录

结构#

从二叉堆的结构说起,它是一棵二叉树,并且是完全二叉树,每个结点中存有一个元素(或者说,有个权值)。

堆性质:父亲的权值不小于儿子的权值(大根堆)。同样的,我们可以定义小根堆。

由堆性质,树根存的是最大值(getmax 操作就解决了)。

节点结构#

对于任意节点 ii
节点的父节点:i12\frac{i-1}{2}
节点的左子节点:2i+12i+1
节点的右子节点:2i+22i+2

过程#

堆的核心操作有两个:

堆化上浮:如果节点比父节点大,则与父节点交换,重复此过程直到根节点。操作结束后,能确保节点满足堆性质。

堆化下沉:在节点的子节点中,找到一个最大的,与该节点交换,重复此过程到底层。操作结束后,能确保节点满足堆性质。

辅助函数”#

int GetParent(int i) => (i - 1) / 2;
int GetLeftChild(int i) => (i * 2) + 1;
int GetRightChild(int i) => (i * 2) + 2;
int GetFirstLeaf(int n) => n / 2;

堆化下沉#

// n 是堆的大小,i 是当前节点的索引
template<typename T>
void ShiftDown(T arr[], int n, int i) {
while (true)
{
int largest = i;
int left = GetLeftChild(i);
int right = GetRightChild(i);
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
if (largest == i)
break;
Swap(arr[i], arr[largest]);
i = largest;
}
}

堆化上浮#

template<typename T>
void ShiftDown(T arr[], int n, int i) {
while (i > 0)
{
int parent = GetParent(i);
if (arr[parent] >= arr[i])
break;
Swap(arr[parent], arr[i]);
i = parent;
}
}

建堆#

上浮实现堆有序

template<typename T>
void Heapify(T arr[], int n) {
for (int i = 0; i < n; i++)
{
ShiftUp(arr, n, i);
}
}

对于第 kk 层的结点,向上调整的复杂度为 O(k)O(k) 而不是 O(logn)O(\log n)

总复杂度:log1+log2++logn=Θ(nlogn)\log 1 + \log 2 + \cdots + \log n = \Theta(n \log n)

上浮操作的复杂度是高于下沉的,所以一般采用下沉实现堆有序。

下沉实现堆有序

template<typename T>
void Heapify(T arr[], int n) {
for (int i = GetFirstLeaf(n) - 1; i >= 0; i--)
{
ShiftDown(arr, n, i);
}
}

每次 合并 两个已经调整好的堆,这说明了正确性。

注意到向下调整的复杂度,为 O(lognk)O(\log n - k),另外注意到叶节点无需调整,因此可从序列约 n/2n/2 的位置开始调整,可减少部分常数但不影响复杂度。

总复杂度为O(n)O(n)

堆排序#

优先级队列#

Reference#

二叉堆 - OI Wiki