文章目录
堆排序
堆排序(Heap Sort)是一种基于二叉堆(Binary Heap)的比较类排序算法,它的时间复杂度为 O(nlogn),并且具有不稳定性。堆排序的核心思想是将待排序的序列构造成一个大顶堆(或小顶堆),此时整个序列的最大值(或最小值)就是堆顶的根节点。接着将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n 个元素的次小值。如此反复执行,便能得到一个有序序列了。
堆排序的过程可以分为两个主要阶段:
建堆(Build Heap):
这个阶段的目标是将一个无序数组转换为一个堆(通常是大顶堆)。
从最后一个非叶子节点开始(即最后一个节点的父节点),向上遍历每个节点,并对每个节点执行"下沉"操作,以确保它们满足堆的性质。
"下沉"操作是指,如果当前节点的值小于其子节点中的较大值,则将其与较大的子节点交换。交换后,继续对交换后的子节点执行相同的操作,直到当前节点大于或等于其所有子节点,或者已经下沉到叶子节点位置。
排序(Heap Sort):
当堆构建完成后,堆顶元素是整个数组的最大值。
将堆顶元素(即数组的第一个元素)与数组的最后一个元素交换,这样最大值就被放到了数组的最后位置。
由于交换后可能破坏了堆的性质,因此需要对剩下的元素(不包括已经排序好的最后一个元素)重新进行堆的调整。
重复上述过程,直到整个数组都被排序。
堆排序的优点包括其高效的时间复杂度 O(nlogn) 和原地排序的特性(即不需要额外的存储空间)。然而,它的缺点是不稳定性,即相等的元素在排序后可能改变它们的相对顺序。此外,由于堆排序涉及到大量的元素交换,因此在某些情况下,其实际性能可能不如其他 O(nlogn) 的排序算法,如快速排序或归并排序。
总的来说,堆排序是一种非常有效的排序算法,特别适用于需要快速找到最大(或最小)值的场景,以及在外部排序中构建初始归并段的情况。
如果以下代码看不太懂,可以看我上一篇文章
代码实现
1. Swap 函数
这个函数用于交换两个整数的值。它接受两个整数指针作为参数,并通过一个临时变量来交换这两个指针所指向的值。
c
// 交换两个整数的值
void Swap(int* a, int* b) {
int tmp = *a; // 将a指向的值存储到临时变量tmp中
*a = *b; // 将b指向的值赋给a指向的变量
*b = tmp; // 将临时变量tmp中的值(即原来a的值)赋给b指向的变量
}
2. AdjustDown 函数
这个函数用于调整堆的结构,确保父节点的值大于或等于其子节点的值(大顶堆)。它接受一个整数数组、数组的大小和一个父节点的索引作为参数。注意如果是升序就建大堆,降序就建小堆,以下代码建的是大堆
c
// 向下调整堆的结构,确保父节点的值大于或等于其子节点的值(大顶堆)
void AdjustDown(int* a, int size, int parent) {
int child = parent * 2 + 1; // 计算左子节点的索引
while (child < size) { // 当子节点的索引小于数组大小时,继续调整
// 如果右子节点存在且其值大于左子节点的值,则更新child为右子节点的索引
if (child + 1 < size && a[child + 1] > a[child]) {
child++;
}
// 如果子节点的值大于父节点的值,则交换它们的位置,并更新parent和child的值
if (a[child] > a[parent]) {
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
} else {
break; // 如果子节点的值不大于父节点的值,则退出循环
}
}
}
3. HeapSort 函数
这个函数实现了堆排序算法。它首先构建一个初始堆,然后不断地将堆顶元素与堆的最后一个元素交换,并重新调整堆的结构,直到整个数组有序。
c
// 堆排序算法实现
void HeapSort(int* a, int n) {
// 构建初始堆,从最后一个非叶子节点开始向上调整堆的结构
for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
AdjustDown(a, n, i);
}
int end = n - 1; // 初始化end为数组的最后一个元素的索引
while (end > 0) {
// 将堆顶元素(最大值)与堆的最后一个元素交换
Swap(&a[0], &a[end]);
// 重新调整堆的结构,确保剩下的元素仍然满足堆的性质
AdjustDown(a, end, 0);
//这里先调整再使end--是因为end在循环里作为下标,到了函数AdjustDown里就作为数据个数使用
//下标刚好比数据个数小一
end--; // 更新end的值,继续处理剩下的元素
}
}
测试用例
c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void PrintArray(int* a, int n)//打印
{
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
void Swap(int* a, int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
//向下调整
void AdjustDown(int* a, int size, int parent) {
int child = parent * 2 + 1;
while (child < size) {//注意!!!
if (child + 1 < size && a[child + 1] > a[child]) {
child++;
}
if (a[child] > a[parent]) {
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else {
break;
}
}
}
//堆排序
void HeapSort(int* a, int n) {
for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
AdjustDown(a, n, i);
}
int end = n - 1;
while (end > 0) {
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
end--;
}
}
void TestSort()
{
int a[] = { 6, 3, 9, 1, 5, 8, 2, 4, 7};
PrintArray(a, sizeof(a) / sizeof(int));//计算数组元素个数并打印
HeapSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
int main() {
TestSort();
return 0;
}