首页 > 编程语言 > 排序算法之堆排序

排序算法之堆排序

时间:2024-01-06 17:32:11浏览次数:47  
标签:childIndex int 复杂度 堆排序 二叉 算法 array 排序 节点

一:概述

堆排序是在二叉堆的基础上实现的,如果想要学习堆排序,就得掌握二叉堆。二叉堆的特性是:最大堆的堆顶是整个堆中最大的元素。最小堆的堆顶是整个堆中最小的元素。

二:具体说明

<1>以最大堆为例讲述

如果要删除一个最大堆的堆顶(并不是完全删除,而是跟末尾的节点交换位置),经过自我调整,第2大元素就会被交换上来,成为最大堆的新堆顶。

                                        排序算法之堆排序_时间复杂度

                                        排序算法之堆排序_二叉堆_02

                                        排序算法之堆排序_堆排序_03

如上图所示,在删除值为10的堆顶节点后,经过调整,值为9的新节点就会顶替上来,在删除值为9的堆顶节点之后,经过调整。值为8的新节点就会顶替上来....由于二叉堆的这个特性,每一次删除旧堆顶,调整后的新堆顶都是大小仅次于旧堆顶的节点。那么只要反复删除堆顶,反复调整二叉堆,所得到的集合就会成为一个有序集合,过程如下:

《1》删除节点9,节点8成为新堆顶。

                                        排序算法之堆排序_堆排序_04

《2》删除节点8,节点7成为新堆顶。

                                        排序算法之堆排序_时间复杂度_05

《3》删除节点7,节点6成为新堆顶。

                                        排序算法之堆排序_时间复杂度_06

《4》删除节点6,节点5成为新堆顶。

                                        排序算法之堆排序_时间复杂度_07

《5》删除节点5,节点4成为新堆顶。

                                        排序算法之堆排序_时间复杂度_08

《6》删除节点4,节点3成为新的堆顶。

                                        排序算法之堆排序_二叉堆_09

《7》删除节点3,节点2成为新堆顶。

                                        排序算法之堆排序_二叉堆_10

到此为止,原本的最大二叉堆已经变成了一个从小到的有序集合。之前说过,二叉堆实际存储在数组中,数组中的元素排列如下。

                                        排序算法之堆排序_二叉堆_11

因此堆排序的算法步骤为:

  • 把无序数组构建成二叉堆。需要从小到大排序,则构建成最大堆;需要从大到小排序,则构建成最小堆。
  • 循环删除堆顶元素,替换到二叉堆的末尾,调整堆产生新的堆顶。

<2>代码实现

/**
     * “下沉”调整
     *
     * @param array       待调整的堆
     * @param parentIndex 要“下沉”的父节点
     * @param length      堆的有效长度
     */
    public static void downAdjust(int[] array, int parentIndex, int length) {
        // temp保存父节点的值,用于最后的赋值
        int temp = array[parentIndex];
        int childIndex = 2 * parentIndex + 1;
        while (childIndex < length) {
            // 如果有右孩子,且右孩子大于左孩子的节点值,则定位到右孩子
            if (childIndex + 1 > length && array[childIndex + 1] > array[childIndex]) {
                childIndex++;
            }
            // 如果父节点大于任何一个孩子的值,则直接跳出
            if (temp >= array[childIndex])
                break;
            // 无须真正交换,单项赋值即可
            array[parentIndex] = array[childIndex];
            parentIndex = childIndex;
            childIndex = 2 * childIndex + 1;
        }
          array[parentIndex] = temp;
    }

    /**
     * 堆排序(升序)
     * @param array 待调整的堆
     */
    public static void heapSort(int[] array) {
        // 1.把无序数组构建成最大堆
        for(int i = (array.length-2)/2; i >= 0; i-- ) {
         downAdjust(array, i, array.length);
        }
        System.out.println(Arrays.toString(array));
        // 2.循环删除堆顶元素,移到集合的尾部,调整堆产生新的堆顶
        for(int i = array.length - 1; i > 0; i-- ) {
           //  最后一个元素和第一个元素进行交换
            int temp = array[i];
            array[i] = array[0];
            array[0] = temp;
           // "下沉"调整最大堆
           downAdjust(array, 0, i);
        }
    }

    public static void main(String[] args) {
            int[] arr = new int[] {1, 3, 23, 6, 5, 7, 8, 9, 10, 0, 23, 56, 100};
            heapSort(arr);
            System.out.println(Arrays.toString(arr));
    }
}

                                        排序算法之堆排序_二叉堆_12

对于上述这个算法,它的空间复杂度是O(1),因为并没有额外开辟集合空间。

时间复杂度的分析:

二叉堆的节点“下沉”调整(downAdjust)方法是堆排序的算法基础.这个调节操作本身的时间复杂度是O(logn)。

堆排序的算法步骤:

  1. 把无序的数组构建成二叉堆。
  2. 循环删除堆顶元素,并将该元素移动到集合的尾部,调整堆产生的新的堆顶。
  • 第一步:把无序的数组构建成二叉堆。这一步的时间复杂度是O(n).
  • 第二步:需要进行n-1次循环。每次循环调用一次downAdjust方法,所以第2步的计算规模是(n -1)xlogn。时间复杂度是O(nlogn)
  • 两个步骤是并列关系哦,所以整体的时间复杂度是O(nlogn)。

<3>堆排序和快速排序的区别和联系

相同点:堆排序和快速排序的平均时间复杂度O(nlogn),并且都是不稳定排序。

不同点:快速排序的最坏时间复杂度是O(n^2),而堆排序的最坏时间复杂度稳定在O(nlogn).

此外,快速排序递归和非递归方法的平均时间复杂度都是O(logn)。而堆时间的空间复杂度是O(1)。

标签:childIndex,int,复杂度,堆排序,二叉,算法,array,排序,节点
From: https://blog.51cto.com/u_15912723/9127319

相关文章

  • 快速排序
    快速排序双指针分治voidquick_sort(intq[],intl,intr){//递归的终止情况if(l>=r)return;//第一步:分成子问题inti=l-1,j=r+1,x=q[l+r>>1];while(i<j){doi++;while(q[i]<x);doj--;while(q[......
  • 算法的时间复杂度和空间复杂度
    1.算法效率1.1如何衡量一个算法的好坏longlongFib(intN){if(N<3){return1;}returnFib(N-1)+Fib(N-2);}斐波那契数列的递归实现方式非常简洁,但是简洁一定好吗?那应该如何衡量其好与坏呢?1.2算法的复杂度衡量一个算法的好坏,一般是从时间和空间上来衡量的,即时间......
  • 经典算法问题之打印日期
    这也是一道经典的算法题。其实也是用两个数组。还有判断是否闰年。两个个循环,外面一个是月份循环,内部一个是每个月的天数循环,然后计数器Count++就行,直到和天数相同就跳出循环,打印就行。#include<stdio.h>intjudge(intyear){if(year%400==0||year%100!=......
  • 机器学习-决策树系列-Adaboost算法-集成学习-29
    目录1.adaboost算法的基本思想2.具体实现1.adaboost算法的基本思想集成学习是将多个弱模型集成在一起变成一个强模型提高模型的准确率,一般有如下两种:bagging:不同的basemodel可以并行计算,输出预测结果少数服从多数,回归问题则对多个模型输出的结果求平均。boosting:后一......
  • 代码随想录算法训练营第二十四天 | 回溯算法理论基础,77. 组合
    一、回溯算法理论基础学习:1.基本概念回溯法是一种搜索方式回溯的本质是穷举,是递归的副产品,即回溯算法就是递归算法回溯解决的问题都能理解成树形结构,一般是在集合中递归查找子集。集合的大小构成树的宽度(n叉树),递归的深度构成了树的深度2.回溯解决的问题(1)组合问题:N个......
  • 经典算法之天数问题
    这题算是非常经典的题目了。无非就是判断闰年然后计算天数而已。用两个month数组记录月份天数一三五七八十腊是31天,二月份非闰年28天,闰年29天,其余都是30天就好了。#include<stdio.h>intjudge(intyear){if(year%400==0||year%100!=0&&year%4==0......
  • C语言逆波兰式算法
    1#include<stdio.h>23//数字模式识别4#defineIS_NUM(c)(((c)>='0')&&((c)<='9')||((c)=='.'))5//符号字符识别6#defineIS_OPERATOR(c)(((c)=='+')||((c)=='-')||((c)==&......
  • 2024年小红书最新x-s-common签名算法分析以及点赞api接口测试nodejs(2024-01-05)
      2024年小红书又更新了x-s-common算法,现在的版本是:3.6.8。这个签名算法现在是越来越重要了,许多接口都要用到。比如:评论,点赞等接口,没有这个算法采集不到数据。  一、chrome逆向x-s-common算法  1、x-s-common  打开chrome,按f12,打开开发者模式,随便找一接口,全局......
  • 经典算法之图形问题
    图形问题的万金解决方法就是创建一个二维数组,然后将填数组,最后打印数组就行了。其本质还是找出图形的规律。首先来找规律,先从外形上来找。奇数高,看图形,是上下左右对称的。所以只找上半区的规律。然后首行比其他行少两个字符也就是多两个空格,最外层都是A,数组可以提前都赋值。只......
  • 排序算法
    冒泡排序思想:1、一个无序的数组,n个元素,一共需要排序n-1轮2、在每一轮中,从数组第0位开始,比较相邻两个元素,如果与需求逆序,就交换这两个元素,在每一轮中,可以将当前最大(最小)的元素交换到最后,3、直到执行完n-1轮,没有需要比较的元素为止。代码实现:publicstaticvoidbubSort(in......