十大经典排序算法总结(堆排序)
Posted 康小庄
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了十大经典排序算法总结(堆排序)相关的知识,希望对你有一定的参考价值。
写在前面
- 楼主整理经典的排序算法
- 记录学习
1. 堆排序(HeapSort)
1.1 概念
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。
1.2 堆的概念
堆是一种特殊的完全二叉树(complete binary tree)。完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示(普通的一般的二叉树通常用链表作为基本容器表示),每一个结点对应数组中的一个元素。
数组索引: 1 2 3 4 5 6 7 8 9 10
节点的值: 16 14 10 8 7 9 3 2 4 1
已知父节点的下标为i
,可以知道
- 左子节点的下标=
2*i
- 右子节点的下标=
2*i+1
已知节点下标为 j
- 父节点的下标=
(j-1)/2
二叉堆一般分为两种:最大堆和最小堆。
最大堆:
最大堆中的最大元素值出现在根结点(堆顶)
堆中每个父节点的元素值都大于等于其孩子结点(如果存在)
最小堆:
最小堆中的最小元素值出现在根结点(堆顶)
堆中每个父节点的元素值都小于等于其孩子结点(如果存在)
1.3 算法描述
堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。在堆中定义以下几种操作:
- 最大堆调整(Max-Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
- 创建最大堆(Build-Max-Heap):将堆所有数据重新排序,使其成为最大堆
- 堆排序(Heap-Sort):移除位在第一个数据的根节点,并做最大堆调整的递归运算 继续进行下面的讨论前,需要注意的一个问题是:数组都是 Zero-Based,这就意味着我们的堆数据结构模型要发生改变
前面的方法做出改变
数组索引: 0 1 2 3 4 5 6 7 8 9
节点的值: 16 14 10 8 7 9 3 2 4 1
已知父节点的下标为i
,可以知道
- 左子节点的下标=
2*i+1
- 右子节点的下标=
2*i+2
示意图
1.4 代码演示
package com.zhuang.algorithm;
import java.util.Arrays;
/**
* @Classname HeapSort
* @Description 堆排序
* @Date 2021/6/13 17:09
* @Created by dell
*/
public class HeapSort {
public static void main(String[] args) {
int[] arr = {51, 46, 20, 18, 65, 97, 82, 30, 77, 50};
heapSort(arr);
System.out.println("堆排序以后的序列为:");
System.out.println(Arrays.toString(arr));
}
//定义堆排序的方法
/**
* @param arr 数组
*/
public static void heapSort(int[] arr) {
int temp = 0;
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
//经过第一次调整 此时最大值位于大顶堆的根节点
for (int j = arr.length - 1; j >= 1; j--) {
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
System.out.println(Arrays.toString(arr));
//递归进行大顶堆的下次调整
//此时从根节点 i=0开始
adjustHeap(arr, 0, j);
}
}
//定义将处于i位置的子树调整成大顶堆的方法
/**
* @param arr 待排序的数组
* @param i 非叶子子节点
* @param len 构建大顶堆的数组参与的元素的长度
*/
public static void adjustHeap(int[] arr, int i, int len) {
//定义临时变量
int temp = arr[i];
// 由于数组下标为0,则若i为某个非叶子节点,左子节点为i*2+1;右子节点为i*2+2
// 构建大顶堆从左到右,自下往上进行
//下次继续选择该节点的左子节点
for (int k = i * 2 + 1; k < len; k = k * 2 + 1) {
//在未越界的情况下,在未越界的情况下:如果当前非叶子节点的左子节点的值小于右子节点的值,则选择最大值,成为新的非叶子父节点
if (k + 1 < len && arr[k] < arr[k + 1]) {
//选择右子节点
k += 1;
}
//进行比较出的左右子节点的最大值和当前父节点的大小进行比较,完成大顶堆
if (arr[k] > temp) {
//将arr[i]设置为最大值
arr[i] = arr[k];
//将i置为k,即下一轮int temp = arr[i] = arr[k],继续循环比较
i = k;
} else {
break;
}
}
//for循环后,将以i为父节点的最大值放在顶部
arr[i] = temp;
}
}
[50, 77, 82, 46, 65, 20, 51, 30, 18, 97]
[18, 77, 51, 46, 65, 20, 50, 30, 82, 97]
[30, 65, 51, 46, 18, 20, 50, 77, 82, 97]
[50, 46, 51, 30, 18, 20, 65, 77, 82, 97]
[20, 46, 50, 30, 18, 51, 65, 77, 82, 97]
[18, 46, 20, 30, 50, 51, 65, 77, 82, 97]
[18, 30, 20, 46, 50, 51, 65, 77, 82, 97]
[20, 18, 30, 46, 50, 51, 65, 77, 82, 97]
[18, 20, 30, 46, 50, 51, 65, 77, 82, 97]
堆排序以后的序列为:
[18, 20, 30, 46, 50, 51, 65, 77, 82, 97]
1.5 算法分析
-
最佳情况:T(n) = O(nlogn)
-
最差情况:T(n) = O(nlogn)
-
平均情况:T(n) = O(nlogn)
1.6 稳定性
堆排序存在大量的筛选和移动过程,属于不稳定的排序算法。
1.7 适用场景
堆排序在建立堆和调整堆的过程中会产生比较大的开销,在元素少的时候并不适用。但是,在元素比较多的情况下,还是不错的一个选择。尤其是在解决诸如“前n大的数”一类问题时,几乎是首选算法。
写在最后
- 学习阶段,描述不当地方,还请大家在评论区指出
- 继续加油💪
以上是关于十大经典排序算法总结(堆排序)的主要内容,如果未能解决你的问题,请参考以下文章