本文目的
上一章节已经详细的向大家介绍过排序的相关概念(详见学习笔记-排序简单介绍) ,本文旨在为大家详细的介绍堆排序。
堆排序
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
堆是什么
堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆是一种非线性结构。可以把堆看作一个数组,也可以被看作一个完全二叉树,通俗来讲堆其实就是利用完全二叉树的结构来维护的一维数组。此处用到的堆一般是指大顶堆或小顶堆。
大顶堆
每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆。如下图所示。
小顶堆
每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图所示。
算法原理
将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值,如此反复执行,便能得到一个有序序列了。需要注意的是建立大顶堆时是从最后一个非叶子节点开始从下往上调整的。
算法流程
1. 创建一个堆 R[0……n-1];
2. 把堆首(最大值)和堆尾互换;
3. 把堆的尺寸缩小 1(已经有序的部分不进入下一轮);
4. 重复步骤 2,直到堆的尺寸为 1。
算法实现
操作过程如下:
1,初始化堆:将R[1..n]构造为堆;
2,将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。
#include #define elemType int /*元素类型*/int k=1;//轮次记录 //打印函数 void Print (elemType arr[], int len){int i;for (i=0; i a[son]){ return;}else{//否则交换父子内容再继续子节点和孙节点比较 swap(&a[dad], &a[son]); dad = son; son = dad * 2 + 1; } }} //排序 void Sort(elemType a[], int len){ int i; //初始化,i从最後一个父节点开始调整 for (i = len / 2 - 1; i >= 0; i--){ max_heapify(a, i, len - 1); } for (i = len - 1; i > 0; i--) { //先将第一个元素和已排好元素前一位做交换,再重新调整,直到排序完毕 swap(&a[0], &a[i]); max_heapify(a, 0, i - 1); printf("第===%d===轮排序后结果如下:",k);Print (a, 9);k=k+1; }}int main() {int i; elemType arr[9] = {94,19,29,9,11,1,14,13,29}; printf("待排序的序列为:"); Print(arr, 9); printf(""); Sort (arr,9); printf(""); printf("排好序的结果如下:"); Print(arr, 9); }
算法分析
时间复杂度
堆排序的运行时间主要是消耗在构建堆和在重建堆时的反复筛选上。在构建堆的过程,因为我们是从完全二叉树最下层的非叶子结点开始构建的,将它与其孩子结点进行比较和有必要的互换,对于每个非叶子结点来说,其实最多2次比较和互换,故初始化堆的时间复杂度为O(n)。在正式排序的时候,第i次取堆顶记录和重建堆需要O(logi)的时间(完全二叉树的某个结点到根结点的距离为log2i+1),并且需要取n-1次堆顶记录,因此重建堆的时间复杂度为O(nlogn)。所以总的来说,堆排序的时间复杂度为O(nlogn)。由于堆排序对元素记录的排序状态不敏感,因此它无论最好,最坏,和平均时间复杂度均为O(nlogn)。
空间复杂度
堆排序中只有交换元素时需要1个辅助空间,与问题规模无关,空间复杂度为O(1)。
排序稳定性
在构建堆的过程中无法保证相同值构建前后的相对位置,所以堆排序是不稳定的。
本文的初衷为学习笔记的分享,部分图文来源于网络,如侵,联删。