十大经典排序算法总结(堆排序)

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大的数”一类问题时,几乎是首选算法。

写在最后

  • 学习阶段,描述不当地方,还请大家在评论区指出
  • 继续加油💪

以上是关于十大经典排序算法总结(堆排序)的主要内容,如果未能解决你的问题,请参考以下文章

十大经典排序算法总结(归并排序)

十大经典排序算法总结(桶排序)

十大经典排序算法总结(希尔排序)

十大经典排序算法总结(快速排序)

十大经典排序算法总结(冒泡排序)

十大经典排序算法的算法描述和代码实现