数据结构与算法(十二):堆排序
一、什么是堆排序#
1.堆,堆排序#
对于“堆”我们可以理解为具有以下性质的完全二叉树:
- 每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆
- 每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
在排序时,一般升序采用大顶堆,降序采用小顶堆。
2.大顶堆#
我们可以看到,层数从小到大,节点的数字是越来越小的,映射到数组有:{50,45,40,20,25,35,30,10,15}
特点是arr[i] >= arr[2*i+1] && arr[i] >= arr[2*i+2]
3.小顶堆#
跟大顶堆相反,层数从小到大,节点的数字是越来越大,映射到数组:{10,20,15,25,50,30,40,35,45}
特点是:arr[i] <= arr[2*i+1] && arr[i] <= arr[2*i+2]
二、堆排序的思路分析#
1.概述#
- 将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。
- 将其与末尾元素进行交换,此时末尾就为最大值。
- 然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
- 遍历构建大顶堆,在这过程中元素的个数逐渐减少,直到最后得到一个有序序列了.
2.举个例子#
对数组{4,6,8,5,9}进行排序。
第一遍排序#
-
我们从最后一个非叶子结点开始排序。第一个非叶子结点为
arr.length/2-1=5/2-1=1
,也就是元素6.,我们对他进行对比并调整位置; -
在{6,5,4}中,5比6小,而9比6大,所以9和6交换位置;
-
接着找到第二个非叶子节点4,由于9是{9,4,8}这个树中最大的,故9与4交换位置
-
由于9与4交换位置打乱了原先{9,5,6}这棵树顺序,所以继续对新树{4,5,6}进行排序
-
由此得到了一个大顶堆,然后将堆顶元素9与末尾元素4进行交换,得到数组
至此,第一遍排序已经完成,我们确定了最大元素9的位置
第二遍排序#
第二遍排序开始时,最大元素9的位置已经确定,实际上要排序的数组变成了{4,6,8,5}
至此,第一遍排序已经完成,我们确定了最第二大元素8的位置
第三遍~第n遍排序#
第二遍排序开始时,最大元素9和第二大元素8的位置已经确定,实际上要排序的数组变成了{5,6,4}
重复比较-排序-交换堆顶和队尾元素位置这一过程,直到最终获得有序数列
三、代码实现#
/**
* @Author:CreateSequence
* @Date:2020-07-16 16:53
* @Description:堆排序
*/
public class HeapSort {
/**
* 对数组进行堆排序
* @param arr
* @return
*/
public static int[] sort(int[] arr) {
//将无序数组构建成一个大/小顶堆
//有几个非叶子节点就排序几次
for (int i = arr.length / 2 - 1; i >= 0; i--) {
sortHeap(arr,i,arr.length);
}
int temp = 0;
//交换数组头尾元素,将最大的元素排沉到队尾
for (int i = arr.length - 1; i > 0; i--) {
//交换头尾元素
temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
//1.交换完后,此时最大的元素在arr[0],最小的元素在arr[i],即确定了本次排序范围最大的数
//2.然后对0~i-1的范围进行排序,重新获得的数组最小的元素在arr[0],最大的元素在arr[i-1]
sortHeap(arr, 0, i);
//3.接着进入下一次循环,重复步骤1,2,每次循环排序范围都缩小一位
}
return arr;
}
/**
* 将以非叶子节点i为根节点的树调整为一个大顶堆
* @param arr 要调整的数组
* @param i 非叶子结点在数组中的下标
* @param length 要调整的数组长度
*/
public static int[] sortHeap(int[] arr, int i, int length) {
if (arr == null || arr.length == 0) {
throw new RuntimeException("数列必须至少有一个元素!");
}
//获取根节点值
int temp = arr[i];
//从左节点开始遍历
for (int j = i * 2 + 1; j < length; j = j * 2 + 1) {
//比较左右节点大小,将j指向值大的节点
if (j + 1 < length && arr[j + 1] > arr[j]) {
j = j + 1;
}
//比较将左右节点与父节点大小
if (arr[j] > temp) {
//如果子节点大于父节点,交换两节点位置
arr[i] = arr[j];
//然后继续从该子节点向下遍历
i = j;
}else {
break;
}
}
//结束循环时,arr[i]已经存放了以原arr[i]为根节点的树的最大值
arr[i] = temp;
return arr;
}
}
作者:Createsequence
出处:https://www.cnblogs.com/Createsequence/p/13325155.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义