玩转堆排序

堆排序

算法概述,算法思想,算法实现与分析展开阐述

如有疏漏,欢迎指出

博主空间icon-default.png?t=M3C8https://blog.csdn.net/JOElib?spm=1011.2266.3001.5343BSTicon-default.png?t=M3C8https://blog.csdn.net/JOElib/article/details/124116947?spm=1001.2014.3001.5501AVLicon-default.png?t=M3C8https://blog.csdn.net/JOElib/article/details/124072495?spm=1001.2014.3001.5501


 

目录

 算法概述🐼

堆排序的定义🦁 

堆排序所需要的条件🦁 

大顶堆🐨

小顶堆🐨 

堆排序的算法思想🐼 

堆排序的算法实现与分析🐼 

1.构建大顶堆🐻 

代码分析与图解:😶‍🌫️ 

2.算法主体🐻 

代码分析与图解:😶‍🌫️ 

完整代码🐼

结论🐼 


算法概述🐼 

堆排序的定义🦁 

  • 堆排序是根据堆中树这种数据结构推演出来的一个排序算法,在实际应用中,我们会通过顺序存 储二叉树来替代真实的二叉树,且堆排序是一种不稳定的排序算法

堆排序所需要的条件🦁 

大顶堆🐨

  • 大顶堆即父节点的值要比其子节点的值都要大(左右子节点的值没有做出要求) 
  • 大顶堆适用于升序排序

小顶堆🐨 

  • 小顶堆即父节点的值要比其子节点的值都要小(左右子节点的值没有做出要求)
  • 小顶堆适用于降序排序 

堆排序的算法思想🐼 

  1. 构建一个大顶堆,此刻根节点的值为最大值
  2. 将根节点与最后一个节点进行交换,最后一个节点的值最大且确定
  3. 将剩余的节点再构建成一个大顶堆,重复以上操作,直到排序完成 

即:构建大顶堆 -> 排序 -> 构建大顶堆 -> 排序..... 

注意:大顶堆其实是从构建局部大顶堆层层递进从而逐步构建的


堆排序的算法实现与分析🐼 

1.构建大顶堆🐻 

    //array代表的是顺序存储二叉树的数组,
    //i是当前节点对应的下标,
    //length是调整元素的长度
    public static void adjustHeap(int[] array,int i,int length) {
        var temp = array[i];
        for (int k = 2*i+1; k < length; k = k*2+1) {
            if (k+1 < length && array[k+1] > array[k]) {
                k++;
            }
            if (array[k] > temp) {
                array[i] = array[k];
                i = k;
            }else {
                break;
            }
        }
        array[i] = temp;
    }

代码分析与图解:😶‍🌫️ 

  1. 先将array[i]即当前节点的值存放到临时变量中
  2. 循环的详解:
    1. k = 2i + 1因为我们想要从当前节点作为根节点去构建一个局部大顶堆,所以我比较当前节点与其左子节点或右子节点的值,根据顺序存储二叉树得,其左子节点是2i+1
    2. 因为两个子节点的值的大小不做要求,所以我们判断两个字节点的较大值,与父节点交换,这样才能保证父节点绝对大于子节点
      1. 因此,k + 1即右子节点的下标,首先,我们要保证有这个节点,即 k + 1 < length(最大的节点的下标是length-1)
      2. 其次,如果右子节点的值array[k+1]比左子节点的值要大,我们就让k++,指向右子节点
    3. 此刻,k指向了我们子节点中最大的值,可以跟父节点进行比较即与temp进行比较
      1. 如果发现子节点的值比父节点的值要大,直接将array[k]赋值给array[i],并把i赋值给k
      2. 否则,直接退出循环,最后将temp赋值给array[i]
        1. 原因是:更高效的进行交换.如果我们每次发现子节点的值大于父节点的值都要交换以下,那么就要交换很多次,效率大大降低
        2. 我们通过k = i,将i不断后移,i到了最后就是最后一个被交换的子节点的位置,直接赋值即可
        3. 我么发现,通过这个方法,我们只需要覆盖,真正交换一次,会比频繁交换的效率高得多
    4. 此刻,有一个问题,为什么要有这个循环?
      1. 我们可以知道,我们构建大顶堆是从构建局部小顶堆开始的,那么说明,我们的局部小顶堆会越来越大,此刻,到达了一定程度的时候,对于某一个节点值的交换会导致其子树的变化,即有可能一个改变导致了树不再成为大顶堆,原理如图所示
      2. 通过下面两张图我们可以清晰的了解到,改变了2与25之后不再是大顶堆,所以要通过循环一层一层的往下去再次核实覆盖,这样才可以构建完整大顶堆

 

2.算法主体🐻 

    public static void heapSort(int[] array) {
        var temp = 0;
        for (int i = array.length/2-1; i >= 0; i--) {
            adjustHeap(array,i,array.length);
        }
        for (int j = array.length-1; j > 0; j--) {
            temp = array[j];
            array[j] = array[0];
            array[0] = temp;
            for (int i = array.length/2-1; i >= 0; i--) {
                adjustHeap(array,i,j);
            }
        }
    }

代码分析与图解:😶‍🌫️ 

  1.  temp临时变量的建立是用于最后一个节点和第一个节点交换
  2. 第一个for循环

     

    1. i = array.length/2 -1的原因是,该下标是最后一棵子树的根节点的下标(通过上图再加上该表达式可得,结果是4,刚好是最后一棵子树的下标)
    2. 为什么这样干?
      1. 由算法思想可知,我们是由局部大顶堆从而构建大顶堆的,所以,我们应该从最后一个子树去弄,从最底层干起,否则无法构建大顶堆
    3. i>=0,为了防止越界且根节点的下标是0,根节点对应的树是大顶堆则完成
    4. i--,从图中可以看出,当i--时,我们会跳跃到另一棵子树,循环的调用构建大顶堆的方法,最终可以得到大顶堆
  3. 第二个for循环
    1. j = array.lenth-1表示最后一个节点
    2. j > 0,原因是,自己和自己交换无意义
    3. j--,原因是每一次都能确定一个最大的元素,调整元素-1
    4. 因为我们已经构建好了大顶堆,根节点一定是最大的,所以将array[0]与array[j]进行交换,我们就能确定最后一个元素,且是最大的元素
    5. 我们再调用调整大顶堆的方法,此时,length应该是j了,因为最后一个元素已经确定了,没必要再去调整位置了,所以调整的元素变成了j
  4. 通过以上的往复操作,就可以完成排序了

完整代码🐼

package datastructure.chapter04.tree.heapsorting;

import java.util.Arrays;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: asus
 * Date: 2022-03-27
 * Time: 8:46
 */
public class HeapSortingDemo {
    public static void main(String[] args) {
        var array = new int[] {3,2,1,6,5};
        heapSort(array);
        System.out.println(Arrays.toString(array));
    }

    public static void heapSort(int[] array) {
        var temp = 0;
        for (int i = array.length/2-1; i >= 0; i--) {
            adjustHeap(array,i,array.length);
        }
        for (int j = array.length-1; j > 0; j--) {
            temp = array[j];
            array[j] = array[0];
            array[0] = temp;
            for (int i = array.length/2-1; i >= 0; i--) {
                adjustHeap(array,i,j);
            }
        }
    }
    public static void adjustHeap(int[] array,int i,int length) {
        var temp = array[i];
        for (int k = 2*i+1; k < length; k = k*2+1) {
            if (k+1 < length && array[k+1] > array[k]) {
                k++;
            }
            if (array[k] > temp) {
                array[i] = array[k];
                i = k;
            }else {
                break;
            }
        }
        array[i] = temp;
    }
}


结论🐼 

         对于快速排序和归并排序来说,堆排序还是较为简单的,只要掌握了大顶堆与顺序存储二叉树等概念就可以快速写出代码,我来总结以下必须掌握的几点:

        1.大顶堆的定义与小顶堆的定义

        2.大顶堆构建时为什么要循环

        3.交换后调整元素如何改变

        数据结构与算法到站啦,接下来我会来个站点大总结哦!

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 25
    评论
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爪哇土著、JOElib

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值