堆排序

    (二叉)堆是一个数组,它可以被看成一个近似的完全二叉树。树上的每一个结点对应数组中的一个元素。除了最底层外,该树是完全充满的,而且是从左到右填充。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+1n2i+2n,即 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];
}
  
};

参考

算法导论

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值