排序算法:详解堆排序算法及其java实现

185 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第29天,点击查看活动详情

作者平台:

| CSDN:blog.csdn.net/qq_4115394…

| 掘金:juejin.cn/user/651387…

| 知乎:www.zhihu.com/people/1024…

| GitHub:github.com/JiangXia-10…

| 微信公众号:1024笔记

本文一共1877字,预计阅读8分钟

前言

前面几篇文章介绍了一些比较经典的排序算法,比如冒泡排序、插入排序、选择排序、希尔排序、归并排序以及速度较快的快速排序,其中稳定的排序算法有:冒泡排序插入排序归并排序,不稳定的排序算法有:选择排序希尔排序快速排序,今天继续学习另一个比较常见的不稳定的排序算法:堆排序算法。

什么是堆排序

在说堆排序之前,首先需要明白堆(heap)这一数据结构。堆是一种特殊的数据结构,是非线性数据结构,相当于一维数组,有两个直接后继。堆需要满足以下条件:

1、堆中的某个结点的值总是不大于或不小于其父结点的值;

2、堆总是一棵完全二叉树。

根结点最大的堆叫做最大堆,根结点最小的堆叫做最小堆。

堆排序则正是指利用堆这种数据结构所设计的一种排序算法。并且根据需要排序的原则和堆的特点,堆排序有两种方法排序方法:

一种是大顶堆:大顶堆中每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;

还有一种是小顶堆:小顶堆中的每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列。

堆排序的基本逻辑是:

1、首先将待排序的数组调整成一个大顶堆或者小顶堆(具体根据需要排序的原则选择,比如这里选择大顶堆),根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;

2、然后将堆顶元素和最后一个元素交换,再将剩下的节点重新构造成一个大顶堆;

3、通过递归操作,重复步骤2,这样从第一次构造大顶堆开始,每一次的构造都能获得一个序列的最大值,然后把它放到大顶堆的尾部。

最后,就得到了一个升序排列的有序的数组了。

堆排序的最好、最坏以及平均时间复杂度都是 Ο(nlogn),并且很显然在每次调整构造堆的过程中相等值元素的位置会发生变化,所以堆排序是一种不稳定的排序算法

堆排序的java实现

通过上述堆排序的思想,对于堆排序采用java代码实现如下:

package com.jiangxia.SortDemo;

import java.util.Arrays;

/**
 * 堆排序算法
 */
public class MaxHeapSort {
    /**
     * 将数组调整为一个大顶堆
     * @param numbers:待排序的数组
     * @param size:数组的大小
     * @param index:元素的索引
     */
    private static void toMaxHeap(int[] numbers,int size,int index) {
        //首先获取左右节点的索引
        int leftNodeIndex=index*2+1;
        int rightNodeIndex=index*2+2;
        //最大节点对应的索引
        int maxIndex=index;
        if(leftNodeIndex<size&&numbers[leftNodeIndex]>numbers[maxIndex]){
            maxIndex=leftNodeIndex;
        }
        if(rightNodeIndex<size&&numbers[rightNodeIndex]>numbers[maxIndex]){
            maxIndex=rightNodeIndex;
        }
        //如果最大索引不等于当前索引,继续调整
        if(maxIndex!=index){
            int t=numbers[maxIndex];
            numbers[maxIndex]=numbers[index];
            numbers[index]=t;
            //递归操作
            toMaxHeap(numbers,size,maxIndex);
        }
    }

    //测试堆排序效果
    public static void main(String[] args) {
        //待排序数组
        int[] numbers = {42, 13, 23, 51, 1, 0, 6, 94, 69, 99};
        //输出排序之前的数组
        System.out.println("堆排序前:\n"+ Arrays.toString(numbers));
        //定义开始调整的位置
        int startIndex=(numbers.length-1)/2;
        //循环开始调整
        for(int i=startIndex;i>=0;i--){
            toMaxHeap(numbers,numbers.length,i);
        }
        //上述的操作之后,待排序数组已经变成了一个大顶堆,需要将根元素和最后一个元素进行调换
        for(int i=numbers.length-1;i>0;i--){
            int t=numbers[0];
            numbers[0]=numbers[i];
            numbers[i]=t;toMaxHeap(numbers,i,0);
        }
        //输出排序之后的数组
        System.out.println("堆排序后 :\n"+Arrays.toString(numbers));
    }
}

结果如下:

image.png

总结

堆排序的核心思想就是根据需要排序的原则(升序还是降序)构造大顶堆和小顶堆的过程,每次构造的堆的根元素就是待排序的数组中最大和最小的元素,并且将该元素与数组最后一个元素进行调换,通过递归操作,如此反复即可得到一个有序的数组了。

其他推荐