1.11 堆排序
1.11.1 介绍
1)堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。**tip:**没有要求结点的左孩子的值和右孩子的值的大小关系
2)**堆排序(Heap Sort)**是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn), 它也是不稳定排序。
3)大顶堆举例
大顶堆与数组之间的转换特点:
arr[i] >= arr[2*i+1]&& arr[i] >= arr[2*i+2]
4)小顶堆举例:
小顶堆与数组之间的转换特点:
arr[i] <= arr[2*i+1]&& arr[i] <= arr[2*i+2]
5)一般升序采用大顶堆,降序采用小顶堆
1.11.2 原理
堆排序的基本思想是:
1)将待排序序列构造成一个大顶堆
2)此时,整个序列的最大值就是堆顶的根节点。
3)将其与末尾元素进行交换,此时末尾就为最大值。
4)然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
- .假设给定 无序序列结构如下
- .此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
- .找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
- 这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
此时,我们就将一个无序序列构造成了一个大顶堆。
步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
- .将堆顶元素9和末尾元素4进行交换
- .重新调整结构,使其继续满足堆定义
- .再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
- 后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
1.11.3 堆排序实现
注:sort.h 在c语言排序总结—前序准备中
堆排序代码实现:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include "sort.h"
int main() {
//生成LENGTH长度的随机数组,并展示出来
int arr[LENGTH];
createRandomArray(arr);
int count = 0;
for (int i = 0; i < LENGTH; i++) {
printf("%-10d", arr[i]);
count++;
if (count % 10 == 0) {
printf("\n");
}
}
printf("\n");
//统计排序的时间
int begin, end;
begin = clock();
//调用排序
heapSort(arr);
end = clock();
//输出排序结果
for (int i = 0; i < LENGTH; i++) {
printf("%-10d", arr[i]);
count++;
if (count % 10 == 0) {
printf("\n");
}
}
//输出排序时间
printf("排序时间为time=%d\n", end - begin);
system("pause");
}
void heapSort(int arr[]) {
int i;
//首先将当前调整为大顶堆
for (i = LENGTH / 2 - 1; i >= 0; i--) {
heapAdjust(arr, i, LENGTH);
}
//交换堆顶元素和堆底元素,然后再次调整为大顶堆
for (i = LENGTH-1; i > 0; i--) {
swap(arr, 0, i);
heapAdjust(arr, 0, i);
}
}
void heapAdjust(int arr[], int i, int newLen) {
int temp,j;
temp = arr[i];//将当前结点保存在temp中
for (j = 2 * i + 1; j < newLen; j = 2 * j + 1) {
if (j+1 < newLen && arr[j] < arr[j + 1]) {//如果右子节点大于左子结点,下标指向右子结点
j++; //j指向右子结点
}
if (arr[j] > temp) { //如果子结点大于父结点
arr[i] = arr[j];//把较大的值赋给当前结点
i = j; //i指向j,继续循环比较
}
else {
break;
}
}
//当for循环结束后,我们已经将以i为父节点的树的最大值,放在了最顶部
arr[i] = temp; //将temp值放到调整后的位置
}
堆排序在10000数据量下的耗时结果为2毫秒:
堆排序在10万数据量下的耗时结果为18毫秒: