之前堆排序我只听说过,但是还一直没有学到,这次来学习一下。
堆排序
定义
百度百科给出的定义:是指利用堆这种数据结构所涉及的一个算法…(这不没说吗)
那我们就来说说堆是什么吧
堆:堆通常是一个可以被看做一颗完全二叉树的数组对象,它的特点是子节点的键值或索引总是小于等于(或大于等于)它的父节点。(这么说就是要么一定比父节点及其直属关系(我发明的词懂就好)的节点大或小)
根节点最大的堆叫最大堆或大根堆,根节点最小的堆叫最小堆或小根堆,对左右子节点的大小关系没有要求。
分析
大致原理:利用大根堆或小根堆思想,首先建立堆,然后将堆首与堆尾交换,堆尾之后为有序区
构建堆中有上浮和下沉的概念
上浮:如果一个节点比父节点大,那么要交换两个节点;交换后可能比它新的父节点还大,因此还要不断进行比较和交换,把这种称为上浮
下沉:类似的,一个结点比原来子节点小,就向下比较和交换,称为下沉;一个结点若有两个子节点,应与最大子节点进行交换
- 构建初始堆,将待排序列构建成一个大根堆(或小根堆),想要结果升序就构建大根堆,想要降序就构建小根堆(后面要与末位交换)
- 此时,序列的的最大值即为堆顶的根节点
- 将堆顶元素与当前堆末尾元素交换,并断开(从待排序列中移除)堆尾元素,(相当于最后的就是已经排好序的部分了,后面再操作就是对尚未排序的部分进行构建堆)此时末尾就是最大值
- 重新构建堆
- 重复2~3,直到待排序列中只剩下一个元素(堆顶元素)
这样最主要的开销就在于每次对于待排序序列的堆构建过程,将根节点和堆尾元素互换只是每次构建堆之后顺手的事(但是也很重要),构建堆的时间复杂度是O(nlogn),故堆排序的时间复杂度也为O(nlogn)。
下面是图片的演示,图上是树的形式,但是我们是用数组存储的,是完全二叉树的层次遍历数组。
这是GIF动图演示
代码
这里直接复用了这篇博客的代码
/**
* @ClassName HeapSort
* @Description 堆排序
* @author 滑技工厂 https://blog.csdn.net/qq_41718454
* @date 2021/2/4
* @version 1.0
*/
public class HeapSort {
/*
* 堆排序:此处以升序排序
* 时间复杂度为O(nlogn)
* 堆:是一种满足一下条件的完全二叉树
* 1.每个节点的值都大于或者等于其左右子节点的值称之为大顶堆;
* 2.每个节点的值都小于或者等于其左右子节点的值称之为小顶堆;
* 3.对于节点的左右子节点的大小关系没有做任何要求;
* 基本思想:
* 1)将待排序的序列构造成一个大顶堆----大顶堆利用前面的顺序存储二叉树的数组结构存储
* 2)此时,序列的的最大值即为堆顶的根节点
* 3)将其与末尾的元素进行交换,此时,末尾就是最大值
* 4)然后再将n-1个元素重新进行构成一个大顶堆,这样可以得到序列的次小值,反复执行,便可以得到一个有序序列
* 5)最终得到升序排列的有序序列
*/
public static void main(String[] args) {
// int[] arr = {5, 1, 7, 3, 1, 6, 9, 4};
int[] arr = {16, 7, 3, 20, 17, 8};
heapSort(arr);
for (int i : arr) {
System.out.print(i + " ");
}
}
/**
* 创建堆,
*
* @param arr 待排序列
*/
private static void heapSort(int[] arr) {
//创建堆
for (int i = (arr.length - 1) / 2; i >= 0; i--) {
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr, i, arr.length);
}
//调整堆结构+交换堆顶元素与末尾元素
for (int i = arr.length - 1; i > 0; i--) {
//将堆顶元素与末尾元素进行交换
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//重新对堆进行调整
adjustHeap(arr, 0, i);
}
}
/**
* 调整堆
*
* @param arr 待排序列
* @param parent 父节点
* @param length 待排序列尾元素索引
*/
private static void adjustHeap(int[] arr, int parent, int length) {
//将temp作为父节点
int temp = arr[parent];
//左孩子
int lChild = 2 * parent + 1;
while (lChild < length) {
//右孩子
int rChild = lChild + 1;
// 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
if (rChild < length && arr[lChild] < arr[rChild]) {
lChild++;
}
// 如果父结点的值已经大于孩子结点的值,则直接结束
if (temp >= arr[lChild]) {
break;
}
// 把孩子结点的值赋给父结点
arr[parent] = arr[lChild];
//选取孩子结点的左孩子结点,继续向下筛选
parent = lChild;
lChild = 2 * lChild + 1;
}
arr[parent] = temp;
}
}
堆排序的思想大概就是这样,如果有什么不同的请大家评论区指出,谢谢!!!