原理
以升序排序为例(升序采用大顶堆,降序采用小顶堆)。可以分成2步:
(1)将待排序的序列构造成一个大顶堆,这时堆顶就是序列的最大值。
(2)将堆顶元素和末尾元素进行交换,这时最大值就放到了末尾。然后将剩下n-1个元素重新构造成一个堆,这样堆顶就是n个元素的次大值。这样反复执行n-1轮后,序列就排好序了。
第1步:将待排序的序列构造成一个堆
思想:从最后一个非叶子节点开始,对于每个非叶子节点,以这个节点为根,自上而下不断调整,使得以它为根的子树成为一个堆。
【那么非叶子节点的范围是多少呢】
答:假设数组有n个元素,则下标为 0 到 n/2-1 的元素是非叶子节点,下标 n/2 到 n-1 的元素是叶子节点。
证明:
方法一:根据完全二叉树的特点,下标为 i 的节点的父节点是 (i-1)/2,那么最后一个节点 n-1 的父节点是 (n-2)/2 = n/2-1,这就是最后一个非叶子节点。
方法二:反证法。假设n/2不是叶子节点,那么它的左节点下标是 2*(n/2)+1 = n+2,超过了数组最大下标n-1,显然不符合逻辑。
第2步
思想:将堆顶元素和末尾元素进行交换,这时最大值就放到了末尾。然后将剩下n-1个元素重新构造成一个堆,这样堆顶就是n个元素的次大值。这样反复执行n-1轮后,序列就排好序了。
【怎么将剩下元素重新构造成一个堆】
剩下元素其实已经是“半个堆”了,只需要把堆顶不断向下交换就行了。每次通过比较当前节点和两个子节点的关系来进行交换。对于大顶堆来说,如果父节点比子节点小,就和较大的子节点进行交换。当父节点比两个子节点都大时就不需要交换了。
代码实现:
// 以index为堆顶,将堆顶元素向下沉,使得[index,max_index)的所有元素重新构造成堆
void heapify(int* nums, int index, int max_index){
while(true){
int maxValueIndex = index; // 当前节点和左右子节点这三者中最大者的下标
// 以大顶堆为例,若父节点比子节点小,就与较大的子节点交换
// 若左节点比父节点(大小顶堆用>号)
if(2*index+1 < max_index && nums[index] < nums[2*index+1])
maxValueIndex = 2*index+1;
// 若右节点比左节点和父节点都大(小顶堆用>号)
if(2*index+2 < max_index && nums[maxValueIndex] < nums[2*index+2])
maxValueIndex = 2*index+2;
if(maxValueIndex == index) // 说明当前节点比两个子节点都大,无需再往下迭代了
break;
swap(nums[index], nums[maxValueIndex]);
index = maxValueIndex;
}
}
void HeapSort(int* nums, int n){
// 1.将原始数组构造成堆
for(int i=n/2-1; i>=0; i--) // 对每个非叶子节点
heapify(nums, i, n); // 调整结构使得以它为根的子树成为堆
// 2.将堆顶移到末尾,并对剩下元素重新构造堆
for(int j=n-1; j>0; j--){
swap(nums[0], nums[j]); // 将堆顶元与末尾元素进行交换
heapify(nums, 0, j); // 剩下元素重新构造堆
}
}
参考: