堆
(二叉)堆是一个数组,它可以被看成一个近似的完全二叉树。树上的每一个结点对应数组中的一个元素。除了最底层外,该树是完全充满的,而且是从左到右填充。heap_size表示有多少个堆元素存储在该数组中。
当根结点下标为0时,结点
i
i
i 的左孩子、右孩子和父亲的下标为:
#define LEFT(i) (((i) << 1) + 1)
#define RIGHT(i) (((i) << 1) + 2)
#define PARENT(i) (((i)-1) >> 1)
![]()
图1 堆
在最大堆中,最大堆性质是指除了根以外的所有结点 i i i 都要满足: A [ P A R E N T ( i ) ] ≥ A [ i ] A[PARENT(i)] \ge A[i] A[PARENT(i)]≥A[i]。最小堆性质是指除了根以外的所有结点 i i i 都有: A [ P A R E N T ( i ) ] ≤ A [ i ] A[PARENT(i)] \le A[i] A[PARENT(i)]≤A[i]。
维护堆的性质
MAX_HEAPIFY是用于维护最大堆性质的重要过程。它的输入为一个数组A和一个下标i。在调用MAX_HEAPIFY的时候,我们假定根结点为LEFT(i)和RIGHT(i)的二叉树都是最大堆,但这时A[i]有可能小于其孩子,这样就违背了最大堆的性质。MAX_HEAPIFY通过让A[i]的值在最大堆中“逐级下降”,从而使得以下标i为根节点的子树重新遵循最大堆的性质。图2图示了MAX_HEAPIFY的执行过程。
![]()
图2 MAX_HEAPIFY
建堆
我们可以用自底向上的方法利用过程MAX_HEAPIFY把一个大小为
n
n
n 的数组
A
[
0..
n
]
A[0..n]
A[0..n] 转化为最大堆。自底向上从第一个非叶子结点开始,依次向前调用MAX_HEAPIFY。过程BUILD_MAX_HEAP对树中的非叶子结点都调用一次MAX_HEAPIFY。
如果结点
i
i
i 是叶子结点,即既没有左孩子也没有右孩子,满足
{
2
i
+
1
≥
n
2
i
+
2
≥
n
\begin{cases} 2i+1\ge n\\ 2i+2\ge n\end{cases}
{2i+1≥n2i+2≥n,即
i
≥
[
n
/
2
]
i \ge[n/2]
i≥[n/2] 是叶子结点。第一个非叶子结点为
i
=
[
n
/
2
]
−
1
i = [n/2]-1
i=[n/2]−1 。
堆排序算法
初始时候,堆排序算法利用BUILD_MAX_HEAP将输入数组A建成最大堆,其中n为数组长度。因为数组中的最大元素总在根结点A[0]中,通过把它与A[n-1]进行互换,我们可以让该元素放在正确的位置。这时候,如果我们从堆中去掉结点n(这一操作可以通过减少heap_size的值来实现),剩余的结点中,原来根的孩子结点仍然是最大堆,而新的根节点可能会违背最大堆的性质。为了维护最大堆的性质,我们要做的是调用MAX_HEAPIFY(A,0),从而在A上构造一个新的最大堆。堆排序算法会不断重复这一过程,直到堆的大小降至2。
堆排序与top k(顺序统计)问题
顺序统计问题是要求从n个元素中选出第k个最小元素。可以建立小根堆,依次进行k次堆排序子过程即可。
以 Leetcode 215 题目为例,代码如下:
class Solution {
public:
#define LEFT(i) (((i)<<1) + 1)
#define RIGHT(i) (((i)<<1) + 2)
int HEAPSIZE;
void MAX_HEAPIFY(vector<int>& nums, int i)
{
int lf, rg;
int largest = -1, tmp;
while (largest != i)
{
lf = LEFT(i);
rg = RIGHT(i);
largest = i;
if (lf < HEAPSIZE && nums[lf] > nums[largest])
largest = lf;
if (rg < HEAPSIZE && nums[rg] > nums[largest])
largest = rg;
if (largest != i)
{
tmp = nums[largest];
nums[largest] = nums[i];
nums[i] = tmp;
i = largest;
largest = -1;
}
}
}
void BUILD_MAX_HEAP(vector<int>& nums)
{
HEAPSIZE = nums.size();
for (int i = nums.size() / 2 - 1; i >= 0; i--)
MAX_HEAPIFY(nums, i);
}
int findKthLargest(vector<int>& nums, int k)
{
int tmp;
BUILD_MAX_HEAP(nums);
for (int i = 1; i < k; i++)
{
tmp = nums[nums.size() - i];
nums[nums.size() - i] = nums[0];
nums[0] = tmp;
HEAPSIZE--;
MAX_HEAPIFY(nums, 0);
}
return nums[0];
}
};
参考
算法导论