目录
[1. 基于建堆的堆排序(需调HPPush)](#1. 基于建堆的堆排序(需调HPPush))
[2. 基于原数组的堆排序(仅调AdjustUp)](#2. 基于原数组的堆排序(仅调AdjustUp))
[3. 堆排序建堆与升降序的总结](#3. 堆排序建堆与升降序的总结)
在专栏前文中,已经实现了堆的基本结构与相关方法;(小根堆)
详见下文:
1. 基于建堆的堆排序(需调HPPush)
当前堆为大根堆,通过对数组元素逐个push进行建堆,再使用HPPop依次出堆顶元素,最终实现对原数组元素的降序排序:
cpp
#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"
void TestHeap1() {
int a[] = { 4,2,8,1,5,6,9,7 };
HP hp;
HPInit(&hp);
// 输出原始数组
printf("orignal array: \n");
for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
printf("%d ", a[i]);
}
printf("\n");
// 建立小根堆,输出其顺序存储
for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
HPPush(&hp, a[i]);
}
printf("minHeap array: \n");
HPPrint(&hp);
// 对原始数组使用堆排序,进行升序输出
int i = 0;
while (!HPEmpty(&hp)) {
//printf("%d ", HPTop(&hp));
a[i++] = HPTop(&hp);
HPPop(&hp);
}
printf("Ascending sort: \n");
for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
printf("%d ", a[i]);
}
printf("\n");
HPDestory(&hp);
}
int main() {
TestHeap1();
return 0;
}
输出结果如下:
![](https://i-blog.csdnimg.cn/direct/f31a0103d0514f5e96931f5b51ba9237.png)
注:1、这种排序思路为实现排序需要额外开辟空间建堆,因为其调用了HPPush方法,空间复杂度为O(N);
2、实现打印升序与降序只需修改向上调整和向下调整方法的对比大小关系,即可实现大根堆的降序和小根堆的升序排序:
![](https://i-blog.csdnimg.cn/direct/bc34b738a1aa46cd83c65bf78147763a.png)
2. 基于原数组的堆排序(仅调AdjustUp)
1、回顾已实现的Adjust方法:
cpp
void AdjustUp(HPDataType* a, int child);
其参数为数组与新插元素的下标,直接将数组视为一个完全二叉树,并不需要依赖于HPPush方法,故并不额外开辟空间,空间复杂度为O(1);
现不调用HPPush方法,即对于已知数组不建堆,基于原数组仅调用向上调整算法进行堆排序。
2、以大根堆为例,调用AdjustUp方法后,在原数组上已经实现了大根堆的顺序存储(但并未申请额外空间),考虑其输出升降序问题:
(1)若想要实现降序排序:
调用AdjustUp方法后,当前数组已满足首元素为最大元素,则输出并删除堆顶的最大元素后,接下来需在剩下的元素中建新大根堆并再删除堆顶最大元素,这与初始考虑的直接删除堆顶元素的HPPop方法思路一致,会导致节点关系的错位与混乱,如此循环往复建堆与调整,虽可以实现最终的排序效果,但显然非常麻烦;
(2)若想要实现升序排序:
调用AdjustUp方法后,当前数组已满足首元素为最大元素,将堆顶元素与最末结点元素交换,再将其位置确定在数组尾,即现确定了最大的元素且保持原堆的大根堆基本关系与结构不变,再进行向下调整即可。如此循环,每次都确定未排序元素中最大的结点,并将其依次从后向前定位在数组中,从而实现了升序排序;
cpp
#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"
// 测试不建堆的排序
void HeapSort(int* a, int n) {
// 建堆
for (int i = 1; i < n; i++) {
AdjustUp(a, i);
}
// 打印大根堆的顺序存储序列
printf("maxHeap array: \n");
for (size_t i = 0; i < n; i++) {
printf("%d ", a[i]);
}
printf("\n");
// 交换首尾并删尾,再进行向下调整
int end = n - 1;
while (end>0) {
Swap(&a[0], &a[end]);
AdjustDown(a, end,0);
end--;
}
// 打印升序排序结果
printf("Ascending sort: \n");
for (int i = 0; i < n; i++) {
printf("%d ", a[i]);
}
printf("\n");
}
int main() {
int a[] = { 4,2,8,1,5,6,9,7 };
int sz = sizeof(a) / sizeof(a[0]);
HeapSort(a,sz);
return 0;
}
运行程序,结果为:
![](https://i-blog.csdnimg.cn/direct/fa6fc8008b3a48e3abac30d57e6b143b.png)
3. 堆排序建堆与升降序的总结
对于实现堆排序:
1、若采用先建堆再排序的方法,则建大根堆实现降序,建小根堆实现升序;(通常不用)
(1)这种方式需要调用HPPush方法,HPPush方法内部自行调用AdjustUp方法,并使用HPTop方法获取堆顶元素并逐个pop以实现降序或升序输出;
(2)这种方式的空间复杂度为O(N);(较高)
2、若采用不建堆直接排序的方法,则建大根堆实现升序,小根堆实现降序;(常用)
(1)这种方式无需调用HPPush方法,直接调用AdjustUp方法,将原数组处理为小根堆或大根堆的顺序存储序列,交换首尾元素并确定尾元素位置后,对其余元素调用AdjustDown方法再确认倒数第二个元素的位置,循环进行直至形成降序或升序排列;
(2)这种方式的时间复杂度为O(N*log N);(较低)