617 字
3 分钟
二叉堆
文章目录
结构
从二叉堆的结构说起,它是一棵二叉树,并且是完全二叉树,每个结点中存有一个元素(或者说,有个权值)。
堆性质:父亲的权值不小于儿子的权值(大根堆)。同样的,我们可以定义小根堆。
由堆性质,树根存的是最大值(getmax 操作就解决了)。
节点结构
对于任意节点
节点的父节点:
节点的左子节点:
节点的右子节点:
过程
堆的核心操作有两个:
堆化上浮:如果节点比父节点大,则与父节点交换,重复此过程直到根节点。操作结束后,能确保节点满足堆性质。
堆化下沉:在节点的子节点中,找到一个最大的,与该节点交换,重复此过程到底层。操作结束后,能确保节点满足堆性质。
辅助函数”
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); }}对于第 层的结点,向上调整的复杂度为 而不是 。
总复杂度:。
上浮操作的复杂度是高于下沉的,所以一般采用下沉实现堆有序。
下沉实现堆有序
template<typename T>void Heapify(T arr[], int n) { for (int i = GetFirstLeaf(n) - 1; i >= 0; i--) { ShiftDown(arr, n, i); }}每次 合并 两个已经调整好的堆,这说明了正确性。
注意到向下调整的复杂度,为 ,另外注意到叶节点无需调整,因此可从序列约 的位置开始调整,可减少部分常数但不影响复杂度。
总复杂度为。