【八大排序】堆排序
作者:互联网
系列文章目录
八大排序(一):直接插入排序
八大排序(二):Shell排序
八大排序(三):冒泡排序
八大排序(四):选择排序
八大排序(五):二路归并排序
八大排序(六):基数排序
目录
前言
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
堆排序其实有些类似于选择排序,通过一次次调整,将待排序序列中最大值或者最小值轮换出来,再放入已排序序列中,但选择方法较为优化。
时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定
一、排序规则
1.概念提醒
这里会涉及一些树和堆的简单用法,在此做出简单介绍,方便大家理解。
完全二叉树
二叉树:如下图所示,即为一个简单二叉树,有一个根节点,其中每个结点存在至多两个子节点。
完全二叉树:简单理解,即是除最外层结点以外的所有结点位置都被占据,且最外层结点从左向右连续存在。
大顶堆(小顶堆)
堆由完全二叉树构成,以数组表达的储存形式,其存在一定的排序概念,即大顶堆(根节点值大于子节点),小顶堆(根节点值小于子节点)。我们的堆排序是以大顶堆,即升序序列作为例子说明。
2.堆的数组表示及调整规则
数组表示
将数组中值与堆结构相对应,有如下规则:
下标为 i 结点的子节点为:2 * i + 1 与 2 * i + 2;
下标为 i 结点的父节点为: (i - 1) / 2;
假设我们有如下数组:
由上述规则可得,下标为0结点的子节点为1和2,1的父节点为0;
以此类推, 则它的堆结构图为:
调整规则
首先,我们需要知道:大顶堆的根节点的值一定是所有值中最大的;
那我们根据这个信息来进行操作,即是每次将堆调整为大顶堆,然后再将值放入已排序序列;
1.从尾部到头部依次调整,因为数据刚开始是极为无序的状态,我们需要对每个堆进行大顶堆调整,使整个堆完成大顶堆的状态,并且只有从尾部开始调整,才能达到层层呈递的效果,即是越往上值越大。
如上图所示,从尾部到头部每个堆进行调整,成为大顶堆;
最后获得一个完全的大顶堆,这时的根节点就是最大值。
2.将调整为根节点的最大值放在数组最后
并且这时根节点在数组下标为0的位置,将其与数组最后的位置的值进行交换即可,类似于选择排序(只是交换的位置不同),将其剔除待排序序列即可。
重复上述1、2操作,直到这个堆只剩下一个结点。
注意、在重复1操作调整大顶堆的时候,只需要调整最大的堆即可,因为只有最大值和数组最后进行交换,而其他部分并没有变化。
二、代码实现
1. 调整函数
函数中先取出需要调整的堆的根结点值tmp,然后向下查找,与两子节点中较大值进行比较,若大于tmp,则放在当前根节点位置,并以当前下标作为新的根节点,并重复这个过程,找到值应该放置的位置。
2.从尾部向头部堆的调整
以数组尾部下标为基准,找到其父结点下标,作为每次调整的根节点,然后向前遍历即可,堆的结束下标因为无法确定,我们直接用数组尾部下标即可。
void HeapAdjust(int arr[], int start, int end)
{
int tmp = arr[start];
for (int i = start * 2 + 1; i <= end; i = start * 2 + 1)//这里语句三是精髓,也就是为什么堆排序的时间复杂度是O(nlongn)的原因
{
if (i < end && arr[i] < arr[i + 1])//如果右孩子存在,且右孩子的值大于左孩子,则让i指向右孩子,不然就一直指向左孩子即可
{
i++;
}
//不管if 成功与否 此时i指向左右孩子中较大的那个孩子
//接着让较大的孩子和tmp比较,如果大于tmp,则向上挪动
if (arr[i] > tmp)
{
arr[start] = arr[i];
start = i; //因为arr[i]的值向上方,则arr[i]就变成新的空白格子,则让start指向新的空白格子
}
else
{
break;//第二种跳出情况:空白位置左右孩子的值都小于tmp的孩子,则跳出循环,准备插入
}
}
//此时,不管是通过for循环语句2(触底退出),还是通过break(左右孩子都小于tmp)
//此时,则可以将tmp插入到空白格子
arr[start] = tmp;
}
void HeapSort(int arr[], int len)
{
assert(arr != NULL);
//1.将其从内到外完整的调整一下(从数组最后一个堆开始)
for (int i = (len - 1 - 1) / 2; i >= 0; i--)//最后一个节点的父节点就是我们需要的最后一个非叶子节点
{
HeapAdjust(arr, i, len - 1);//因为第三个值没有规律,所以统一写len-1,并且没有任何影响
}
//根节点的值和最后一个节点的值进行交换,然后将最后一个节点剔除掉
for (int i = 0; i < len - 1; i++)//7个值 剔除6次即可 len个值 剔除len-1次 O(n)
{
int tmp = arr[0];
arr[0] = arr[len - 1 - i];
arr[len - 1 - i] = tmp;
HeapAdjust(arr, 0, (len - 1 - i) - 1);//len-1-i 代表当前最后一个节点的下标,-1代表将其剔除循环
}
}
总结
以上就是今天学习的内容,本文介绍了堆排序的排序规则和代码实现,堆排序速度较快,可用于排序算法的优化。
标签:tmp,arr,八大,堆排序,结点,len,排序,节点 来源: https://blog.csdn.net/W1024201/article/details/123610545