sort-04-heap sort 堆排序算法详解

简介: 这是一个关于排序算法的系列文章摘要,包括了10篇关于不同排序算法的链接,如冒泡排序、快速排序、选择排序、堆排序等。堆排序是一种基于比较的排序算法,利用了近似完全二叉树的结构并满足最大堆或最小堆的性质。最大堆中,每个节点的值都大于或等于其子节点。文章详细解释了最大堆的概念、节点访问方式以及堆的操作,包括堆调整和堆排序的过程,并通过图解展示了堆排序的具体步骤。此外,还提供了一个Java实现的堆排序代码示例。整个排序系列来源于一个开源项目,读者可以通过链接查看完整内容。

排序系列

sort-00-排序算法汇总

sort-01-bubble sort 冒泡排序算法详解

sort-02-QuickSort 快速排序到底快在哪里?

sort-03-SelectSort 选择排序算法详解

sort-04-heap sort 堆排序算法详解

sort-05-insert sort 插入排序算法详解

sort-06-shell sort 希尔排序算法详解

sort-07-merge sort 归并排序

sort-08-counting sort 计数排序

sort-09-bucket sort 桶排序

sort-10-bigfile 大文件外部排序

堆排序

堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。

堆是一个近似完全二叉树的结构,并同时满足堆的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。

基础知识 JCIP-11-二叉堆

最大堆

若以升序排序说明,把数组转换成最大堆(Max-Heap Heap),这是一种满足最大堆性质(Max-Heap Property)的二叉树:对于除了根之外的每个节点i, A[parent(i)] ≥ A[i]

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

重复从最大堆取出数值最大的结点(把根结点和最后一个结点交换,把交换后的最后一个结点移出堆),并让残余的堆维持最大堆性质。

输入图片说明

同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子:

输入图片说明

堆节点的访问

通常堆是通过一维数组来实现的。

在数组起始位置为0的情形中:

则父节点和子节点的位置关系如下:

(01) 索引为i的左孩子的索引是 (2*i+1);

(02) 索引为i的左孩子的索引是 (2*i+2);

(03) 索引为i的父结点的索引是 floor((i-1)/2);

堆节点的访问

堆的操作

在堆的数据结构中,堆中的最大值总是位于根节点(在优先队列中使用堆的话堆中的最小值位于根节点)。

堆中定义以下几种操作:

最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点

创建最大堆(Build Max Heap):将堆中的所有数据重新排序

堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算

堆排序算法图解

这个图解来自 图解排序算法(三)之堆排序,画的非常漂亮。

基本思想

将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。

将其与末尾元素进行交换,此时末尾就为最大值。

然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

步骤

步骤一 构造初始堆

将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

a. 假设给定无序序列结构如下

输入图片说明

b. 此时我们从最后一个非叶子结点开始(叶子结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。

输入图片说明

c. 找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。

输入图片说明

d. 这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。

输入图片说明

此时,我们就将一个无序的序列构造成了一个大顶堆。

步骤二 调整堆

将堆顶元素与末尾元素进行交换,使末尾元素最大。

然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

a. 将堆顶元素9和末尾元素4进行交换

输入图片说明

b. 重新调整结构,使其继续满足堆定义

输入图片说明

c. 再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.

输入图片说明

后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

输入图片说明

简单总结

再简单总结下堆排序的基本思路:

a. 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

b. 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

c. 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

java 实现

说明

为了和前面的逻辑保持一致,我们暂时依然使用 list 去实现这个堆排序。

实现

package com.github.houbb.sort.core.api;

import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.sort.core.util.InnerSortUtil;

import java.util.List;

/**
 * 堆排序
 *
 * @author binbin.hou
 * @since 0.0.4
 */
public class HeapSort extends AbstractSort {
   
   

    private static final Log log = LogFactory.getLog(HeapSort.class);

    @Override
    @SuppressWarnings("all")
    protected void doSort(List<?> original) {
   
   
        final int maxIndex = original.size() - 1;

        /*
         *  第一步:将数组堆化
         *  beginIndex = 第一个非叶子节点。
         *  从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。
         *  叶子节点可以看作已符合堆要求的节点,根节点就是它自己且自己以下值为最大。
         */
        int beginIndex = original.size() / 2 - 1;
        for (int i = beginIndex; i >= 0; i--) {
   
   
            maxHeapify(original, i, maxIndex);
        }

        /*
         * 第二步:对堆化数据排序
         * 每次都是移出最顶层的根节点A[0],与最尾部节点位置调换,同时遍历长度 - 1。
         * 然后从新整理被换到根节点的末尾元素,使其符合堆的特性。
         * 直至未排序的堆长度为 0。
         */
        for (int i = maxIndex; i > 0; i--) {
   
   
            InnerSortUtil.swap(original, 0, i);
            maxHeapify(original, 0, i - 1);
        }
    }

    /**
     * 调整索引为 index 处的数据,使其符合堆的特性。
     *
     * @param list  列表
     * @param index 需要堆化处理的数据的索引
     * @param len   未排序的堆(数组)的长度
     * @since 0.0.4
     */
    @SuppressWarnings("all")
    private void maxHeapify(final List list, int index, int len) {
   
   
        int li = (index * 2) + 1; // 左子节点索引
        int ri = li + 1;           // 右子节点索引
        int cMax = li;             // 子节点值最大索引,默认左子节点。

        // 左子节点索引超出计算范围,直接返回。
        if (li > len) {
   
   
            return;
        }

        // 先判断左右子节点,哪个较大。
        if (ri <= len && InnerSortUtil.gt(list, ri, li)) {
   
   
            cMax = ri;
        }

        if (InnerSortUtil.gt(list, cMax, index)) {
   
   
            InnerSortUtil.swap(list, cMax, index);      // 如果父节点被子节点调换,
            maxHeapify(list, cMax, len);  // 则需要继续判断换下后的父节点是否符合堆的特性。
        }
    }

}

测试

List<Integer> list = RandomUtil.randomList(10);
System.out.println("开始排序:" + list);
SortHelper.heap(list);
System.out.println("完成排序:" + list);

日志如下:

开始排序:[48, 6, 85, 16, 93, 13, 0, 68, 68, 18]
完成排序:[0, 6, 13, 16, 18, 48, 68, 68, 85, 93]

开源地址

为了便于大家学习,上面的排序已经开源,开源地址:

https://github.com/houbb/sort

欢迎大家 fork/star,鼓励一下作者~~

小结

堆排序是一种选择排序,整体主要由构建初始堆+交换堆顶元素和末尾元素并重建堆两部分组成。其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)...1]逐步递减,近似为nlogn。

所以堆排序时间复杂度一般认为就是O(nlogn)级。

ps: 个人理解一般树的数据结构,时间复杂度都是 logn 级别的。

希望本文对你有帮助,如果有其他想法的话,也可以评论区和大家分享哦。

各位极客的点赞收藏转发,是老马持续写作的最大动力!

相关文章
|
5月前
|
算法 Python
数据结构算法--4堆排序
堆排序过程概述:建立大根堆,将堆顶最大元素移出并替换为末尾元素,调整保持堆性质,重复此过程直至堆为空,实现排序。时间复杂度为O(nlogn)。Python中可用heapq模块进行堆操作。
|
1月前
|
算法 搜索推荐
数据结构与算法学习十八:堆排序
这篇文章介绍了堆排序是一种通过构建堆数据结构来实现的高效排序算法,具有平均和最坏时间复杂度为O(nlogn)的特点。
70 0
数据结构与算法学习十八:堆排序
|
1月前
|
算法 搜索推荐
算法之堆排序
本文介绍了堆排序算法的原理和实现,通过构建最大堆或最小堆,利用堆的性质进行高效的排序,并提供了具体的编程实现细节和示例。
21 0
算法之堆排序
|
1月前
|
算法 Java Go
深入了解堆排序算法
深入了解堆排序算法
22 1
|
6月前
|
移动开发 算法 前端开发
前端算法之堆排序
前端算法之堆排序
40 1
|
3月前
|
搜索推荐 算法 Java
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
该博客文章通过UML类图和Java源码示例,展示了如何使用适配器模式将QuickSort类和BinarySearch类的排序和查找功能适配到DataOperation接口中,实现算法的解耦和复用。
40 1
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
|
5月前
|
搜索推荐 算法 Java
Java中的快速排序、归并排序和堆排序是常见的排序算法。
【6月更文挑战第21天】Java中的快速排序、归并排序和堆排序是常见的排序算法。快速排序采用分治,以基准元素划分数组并递归排序;归并排序同样分治,先分割再合并有序子数组;堆排序通过构建堆来排序,保持堆性质并交换堆顶元素。每种算法各有优劣:快排平均高效,最坏O(n²);归并稳定O(n log n)但需额外空间;堆排序O(n log n)且原地排序,但不稳定。
47 3
|
5月前
|
搜索推荐 算法
【C/排序算法】:堆排序和选择排序
【C/排序算法】:堆排序和选择排序
34 0
|
5月前
|
存储 算法 C语言
数据结构和算法——堆排序(选择排序、思路图解、代码、时间复杂度、堆排序及代码)
数据结构和算法——堆排序(选择排序、思路图解、代码、时间复杂度、堆排序及代码)
36 0
|
6月前
|
搜索推荐 算法 Java
sort-05-insert sort 插入排序算法详解
这是一个关于排序算法的系列文章总结,包括冒泡排序、快速排序、选择排序、堆排序、插入排序等10种排序算法的详细讲解和Java实现。插入排序是一种简单直观的排序算法,通过构建有序序列并逐个插入新元素来排序。提供的Java代码展示了插入排序的实现过程,并附有测试示例。所有算法已开源在GitHub项目[https://github.com/houbb/sort](https://github.com/houbb/sort)中。