建堆&堆排序实现

3,731 阅读2分钟

堆分为最大堆和最小堆,本文以最大堆为例

建堆

最大堆是在完全二叉树的基础上满足父节点大于子节点的一种树结构,最大值在堆顶

根据完全二叉树特性,除了叶子层其他层都满足有2的n-1次方节点,所以堆底层是用数组实现的,堆顶元素下标为0。下标为i的父节点,左节点下标为(2*i+1),右节点下标(2*i+2)

给一个乱序数组如何建堆呢?如果从堆顶下溯,存在如下图所示情况,得到的不是最大堆,所以应该从堆底至堆顶上溯,防止下层有比上层更大的数


//从i作为父节点建堆,堆尺寸为size,即从nums[0]~nums[size-1]
void heapSort(vector<int>& nums, int i,int size){
	int l = 2 * i + 1;
	int r = 2 * i + 2;
	int max = i;
	if (l<size && nums[l]>nums[max])
		max = l;
	if (r<size && nums[r]>nums[max])
		max = r;
	if (max != i){
		swap(nums[i], nums[max]);
		heapSort(nums, max, size);
	}
}
void makeHeap(vector<int>& nums){
	int n = nums.size();
	for (int i = n / 2; i >=0; i--){
		heapSort(nums, i, n);//从低至上建堆
	}
}

堆排序

现在数组里面是最大堆,如何对数组排序使得元素按顺序存储呢?

将堆顶与堆底元素互换,对堆顶进行建堆(尺寸减掉堆底元素)重复此步骤直至堆尺寸为1

类似于选择排序,每次取出最大值放在数组后面

void sortHeap(vector<int>& nums){
	int n = nums.size();
	for (int i = n-1; i > 0; i--){
		swap(nums[0], nums[i]);
		heapSort(nums, 0, i);
	}
}

需要注意的是堆排序之后,数组里面就不是最大堆了

建堆时间复杂度O(n)

假如有N个节点,那么高度为H

最后一层每个父节点最多只需要下调1次,最后一层父节点共有2^(H-1)个

倒数第二层最多只需要下调2次,倒数第二层有2^(H-2)个节点

顶点最多需要下调H次,,顶点只有1(2^0)个

所以总共的时间复杂度为s = 1 * 2^(H-1) + 2 * 2^(H-2) + ... + (H-1) * 2^1 + H * 2^0



堆排序时间复杂度O(nlgn)

这里的堆排序为将最大堆最大元素与堆尾元素交换在重新建堆,直到堆的元素个数为0,每次从堆顶重建堆,向下遍历lgn次,一共n个元素,虽然随着堆变小,n会变小,但是级别还是在O(nlgn)

堆排序空间复杂度O(1)

堆排序是在原数组中就地排序,不需要辅助空间,所以O(1)