经典算法09 堆排序
活动地址:CSDN21天学习挑战赛
*学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。
简介
堆排序和简单选择排序一样属于选择排序
简单排序:每一趟在待排序的元素中选取关键字最小(或最大)的元素加入有序子序列
堆排序:每一趟将堆顶元素加入有序子序列(与待排序序列中的最后一个元素交换)并将待排序元素序列再次调整为大根堆(小元素不断“下坠”)
堆排序基于一个叫做堆的数据结构,所以我们先看什么是堆
堆
定义:
若n个关键字序列L[1…n〕 满足下面某一条性质,则称为堆 (Heap)
①若满足:L(i)≥L(2i)且L≥l(2i+1) (1≤i≤n/2)—— 大根堆(大顶堆)
②若满足:L(i)≤L(2i)且L(i)≤L(2i+1) (1≤i ≤n/2)——小根堆(小顶堆)
可以结合完全二叉树的顺序存储来理解,即大根堆位完全二叉树中,根大于等于左和右的情况,小根堆位根小于等于左右节点情况,如下图所示
(图片来自王道考研)
大根堆
在我们知道了堆的概念之后,我们所需要做的就是对于所给出的初始序列,建立大根堆,即把所有非终端节点都检查一遍,是否满足大根堆的要求,如果不满足,则进行调整
分析:
给定初始序列,在顺序存储的完全二叉树中,非终端节点编号i<=[n/2]
(图片来自王道考研)
首先,先处理四号节点,,检查当前节点是否满足根>=左、右,若不满足,将当前节点与更大一个孩子互换,他只有一个孩子即和31进行互换
接着看三号节点,孩子的下标为2i,2i+1即6和7,78大于67但小于87,不符合大根堆特性,即78和87互换
(图片来自王道考研)
接下来处理二号节点,17小于32,也小于45,我们选择更大的45与17互换,如果换32,45大于32,不符合大根堆的特性
(图片来自王道考研)
最后处理一号节点,87更大,所以互换
(图片来自王道考研)
此时问题发生了,53下落以后,导致下一层的子树不符合大根堆的要求,此时我们需要继续调整,是53和78互换
(图片来自王道考研)
##代码实现
//建立大根堆
void BuildMaxHeap(int A[], int len){
for(int i=len/2;i>0;i--)
HeadAdjust (A, i, len); //从后往前调整所有非终端结点
}
//将以 k 为根的子树调整为大根堆
void HeadAdjust(int All, int k, int len){
A[0]=A[k]; //A[0]暂存子树的根结点
for(int i=2*k;i<=len; i*=2){ //取key较大的子结点向下筛选
if(i<len&&A[i]<A[i+1])
i++; //取key较大的子结点的下标
if(A[0]>=A[i])
break; //筛选结束
else{
A[k]=A[i]: //将Ard]调整到双亲结点上
k=i; //修改k值,以便继续向下筛选
}
A[k]=A[0]: //被筛选结点的值放入最终位置
}
##基于大根堆进行排序
堆排序:每一趟将堆顶元素加入有序子序列(与待排序序列中的最后一个元素交换)
分析:
基于上述,我们建立了大根堆如下图所示
(图片来自王道考研)
我们需要将09和87进行交换,87换到了末尾,87不需要进行变动了
(图片来自王道考研)
此时09到了堆顶,上面的一部分已经不是一个大根堆了,所以我们需要对09进行下坠,09的右孩子大,所以和78进行互换,再和65进行互换
(图片来自王道考研)
此时,上面部分又成了大根堆,和上面一样,将堆顶的元素和末尾的元素进行交换
(图片来自王道考研)
此时53到了堆顶,又不满足大根堆的特性,所以继续上述的操作,将剩余部分变成大根堆
(图片来自王道考研)
第三趟,堆顶元素65和堆底元素09进行互换,09继续下坠形成大根堆
(图片来自王道考研)
第四趟,堆顶元素53和堆底元素17进行互换,17继续下坠形成大根堆
(图片来自王道考研)
第五趟,堆顶元素45和堆底元素17进行互换,17继续下坠形成大根堆
(图片来自王道考研)
第六趟,堆顶元素32和堆底元素09进行互换,09继续下坠形成大根堆
(图片来自王道考研)
第七趟,堆顶元素17和堆底元素09进行互换,此时已经完成,不需要进行调整了
(图片来自王道考研)
对于n个元素的序列,我们进行了n-1趟的处理,得到递增的序列
如果使用小根堆,则得到递减的序列
基于大根堆进行排序(代码)
//建立大根堆
void BuildMaxHeap(int A[], int len)
//将以 k 为根的子树调整为大根堆
void HeadAdjust (int A[],int k, int len)
//堆排序的完整逻辑
void HeapSort (int A[], int len){
BuildMaxHeap (A, len); //初始建堆
for(int i=len;i>1;i--){ //n-1趟的交换和建堆过程
swap(A[i],A[1]) ; //堆顶元素和堆底元素交换
HeadAdjust (A,1, i-1) ; //把剩余的待排序元素整理成堆
}
}
算法效率分析:
结论: 一个结点,每“下坠”一层,最多只需对比关键宇2次
若树高为h,某结点在第i层,则将这个结点向下调整最多只需要“下坠”h-i层,关键字对比次数不超过 2(h-i)
(图片来自王道考研)
故建堆的过程,关键字对比次数不超过4n,建堆时间复杂度=O(n)
(图片来自王道考研)
空间复杂度=O(1)
稳定性:不稳定
总结:
(图片来自王道考研)