排序算法 —— 堆排序(图文超详细)

article/2025/10/29 22:36:48

文章目录

  • 堆排序
    • 1. 排序思路
    • 2. 如何建成大根堆
      • 2.1. 将待排序的数据看成一个完全二叉树。
      • 2.2. 建堆思想
      • 2.3. 建堆步骤
        • 2.3.1.先将最后一棵子树建成大根堆
        • 2.3.2.将其余子树建成大根堆
        • 2.3.3. 代码分析
    • 3 如何实现堆排序
      • 3.1 排序思路
      • 3.2 代码实现的思路
      • 3.3 代码分析
      • 3.4 排序过程
    • 4. 整体代码展示

堆排序

1. 排序思路

  • 将一组数据建成大根堆。
  • 定义一个end来记录此时堆末尾元素的位置。
  • 每排序好一个,end值就减一个。
  • 在排序的过程中要保障堆时刻为大根堆。
  • 开始排序时,也要保证是大根堆。

待排序的数据:27, 15, 19, 18, 28, 34, 65, 49, 25, 37

因为堆实际上是在二叉树的基础上做的一些调整。
在操作堆的时候本质是利用了二叉树的思想。

2. 如何建成大根堆

给每一个数据一个下标值。

2.1. 将待排序的数据看成一个完全二叉树。

下图是排列好的二叉树。

但是它此时既不是大根堆也不是小根堆,即不是一个堆。

要将它变成一个堆,要先将它变成大根堆或者小根堆。

2.2. 建堆思想

如果是要建立成大根堆,先将它的子树全部建成大根堆。此时,这棵树整体也就变成了一个大根堆。


先将图中画红圈的建成大根堆,建成后,整棵树就是一个大根堆了。

有两个方法:

  1. 从无到有的建立堆,获取一个数据的同时将它建立为一个大根堆。一边获取数据,一边的建堆。
    如果有两个数据,就将两个数据建成堆;若四个数据,就将四个数据建成堆。
  2. 先从最后一棵子树的根节点开始调整,将它建成大根堆;再将它前面的结点逐渐建成大根堆;最后整体就是一个大根堆了。(向下调整)

2.3. 建堆步骤

实现向下调整

2.3.1.先将最后一棵子树建成大根堆

  • 最后一棵子树的孩子结点的下标是数组的长度减1。(10 - 1)
  • 已知孩子结点,求父亲结点的公式:(i - 1) / 2,i 是孩子结点的下标。
  • 现在得到了最后一棵子树的孩子和父亲结点的下标。(孩子:9,父亲:4)
  • 比较两个结点的值,将较大的结点交换到对应父节点的位置。
  • 若父节点为最大值,则不需要交换。

最后一棵子树建堆后:

图中的4下标和9下标的结点进行了交换。

2.3.2.将其余子树建成大根堆

第1步:

  • r 记录父亲结点的下标;i 记录左右最大值的下标。
  • r - - ,找到新的子树的根节点。
  • 找到了根节点,可以访问到子节点。
    在这里插入图片描述


第2步:

  • i 结点始终记录左右子节点的最大值。
  • 将这个最大值节点与当前 r 节点交换。


    交换后:

下图的3下标和7下标交换了。


第3步:

  • r - -,r 指向了2下标所在根节点的位置。
  • i 指向左右子节点的最大值。



第4步:

  • 交换 r 和 i 指向的两个结点。

第5步:

  • r - -,r 指向了1下标所在根节点的位置。
  • i 指向左右子节点的最大值。



第6步:

  • 交换 r 和 i 指向的两个结点。


走到这里就会发现,3下标位置的根节点又不是大根堆了。
要让 r 重新指向3下标,i 再指向左右孩子最大值。


第7步:

  • r 指向 i ,i 指向(2 * p + 1)的位置(下标7处),p 代表的是父节点下标。
  • i 再指向左右孩子最大值。


第8步:

  • 比较 r 和 i 之后,交换较大到根节点位置。


程序并不知道这一步交换完成后,15有没有左右节点了。
因此并不能确定交换一次 r 和 i 结点之后就为大根堆了。


解决办法:

  • r 指向 i 的位置,i 指向 (2 * p + 1) 的位置(下标17处),p 代表的是父节点下标。
  • i 再指向左右孩子最大值。


此时 i 的值大于数据个数(10)的最大下标值(9),此时说明15左右没有孩子。
此时如果 i 的值乘2加1大于9,就说明此时的 i 指向的就是最后一个结点了。

2.3.3. 代码分析

 for (int parent = (array.length - 1 - 1) / 2; parent >= 0 ; parent--) {}
  • parent 代表的是父节点 r 。
  • 先求出数组长度,减1表示得到末尾元素下标,再减1表示得到每一个父节点。
  • 当 parent 小于0时,调整完毕。
 shiftDown(array, parent, array.length);
  • 循环调用向下调整方法。
 public static void shiftDown(int[] array, int parent, int len) {}
  • 参数 len 是每棵子树调整的结束位置。
  • 结束位置不能大于 len 。
int child = (2 * parent) + 1;
  • 求得孩子结点,代表的是 i 。
 while (child < len) {if (child + 1 < len && array[child] < array[child + 1]) {child++;}}

  • child < len 保证有左孩子,len 是数组长度。
  • child + 1 < len 保证不越界。
  • array[child] < array[child + 1] 保证 child 是左右最大值。
if (array[child] > array[parent]) {
}
  • 比较孩子结点和父节点的值。
 if (array[child] > array[parent]) {swap(array, child, parent);parent = child;child = 2 * parent + 1;}
  • 如果孩子结点大于父节点就交换这两个结点。
  • 父节点指向子节点。
  • 孩子结点指向新的位置。
public static void swap(int[] array, int i, int j) {int tmp = array[i];array[i] = array[j];array[j] = tmp;
}
  • 交换方法的实现。

最终实现的大根堆:

3 如何实现堆排序

3.1 排序思路

  • 将堆顶元素与堆的最后一个元素交换,此时堆的末尾位置就是最大的元素了。
  • 每一次交换后要保证堆是大根堆。
  • 若堆不是大根堆,就调用向下调整重新建成大根堆。
  • 重新交换堆顶元素与堆的末位置元素。

3.2 代码实现的思路

  • 定义一个 end 来记录此时堆的末尾位置元素的位置。(元素个数 - 1)
  • 每排序好一个数据,end 就减去1。
  • 若此时的堆不是大根堆,调用向下调整重新实现为大根堆。
  • 若刚开始排序时,堆不为大根堆,则要将它建成大根堆。

3.3 代码分析

 creatBigHeap(array);
  • 先调用建堆方法建堆。
int end = array.length - 1;
  • 数组长度减1求的是堆末尾位置元素的下标。
 while (end > 0) {swap(array, 0, end);shiftDown(array, 0, end);end--;}
  • 循环调用交换方法交换0下标和end位置的值。
  • 0 下标即使堆顶元素的位置。
  • 循环调用向下调整方法建成大根堆。
  • 每次交换后,end每次减少一个。

3.4 排序过程

  1. 交换一次后

  2. 此时不为大根堆了,先建成大根堆,再交换。
    在这里插入图片描述

  3. 65 是排序好的了,所以在重新建成大根堆的时候不需要处理65,交换第2次。

  4. 按照上面的步骤会逐渐排成:
    在这里插入图片描述

4. 整体代码展示

 public static void heapSort(int[] array) {creatBigHeap(array);int end = array.length - 1;while (end > 0) {swap(array, 0, end);shiftDown(array, 0, end);end--;}}//建堆
public static void creatBigHeap(int[] array) {for (int parent = (array.length - 1 - 1) / 2; parent >= 0 ; parent--) {//调用向下调整shiftDown(array, parent, array.length);}
}//向下调整的方法
public static void shiftDown(int[] array, int parent, int len) {int child = (2 * parent) + 1;while (child < len) {if (child + 1 < len && array[child] < array[child + 1]) {child++;}if (array[child] > array[parent]) {swap(array, child, parent);parent = child;child = 2 * parent + 1;}else {break;}}
}//交换的方法
public static void swap(int[] array, int i, int j) {int tmp = array[i];array[i] = array[j];array[j] = tmp;
}

在这里插入图片描述


http://chatgpt.dhexx.cn/article/SLEaiGSR.shtml

相关文章

十大经典排序算法----堆排序(超详细)

目录 1. 堆排序的基础知识 1.1 大顶堆&&小顶堆 1.2 向下调整算法 1.3 物理结构与逻辑结构的关系 2. 堆排序详解 2.1 堆排序整体思路 2.2 思路详解 2.2.1 建堆 2.2.2 大堆or小堆 2.2.3 输出数据 3. 时间复杂度分析 4. 完整代码 5. 彩蛋 1. 堆排序的基础知识…

【转载】堆排序算法(图解详细流程)

堆排序的时间复杂度O(N*logN),额外空间复杂度O(1)&#xff0c;是一个不稳定性的排序 目录 一 准备知识 1.1 大根堆和小根堆 二 堆排序基本步骤 2.1 构造堆 2.2 固定最大值再构造堆 三 总结 四 代码 一 准备知识 堆的结构可以分为大根堆和小根堆&#xff0c;是一个完全…

堆排序算法(图解详细流程)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 堆排序基本介绍大顶堆举例说明小顶堆举例说明堆排序的基本思想堆排序步骤图解说明堆排序的基本思路总结 堆排序基本介绍 堆排序是利用堆这种数据结构而设计的一种排…

堆排序详细图解(通俗易懂)

什么是堆 堆是一种叫做完全二叉树的数据结构&#xff0c;可以分为大根堆&#xff0c;小根堆&#xff0c;而堆排序就是基于这种结构而产生的一种程序算法。 堆的分类 大根堆:每个节点的值都大于或者等于他的左右孩子节点的值 小根堆:每个结点的值都小于或等于其左孩子和右孩子…

PreferenceActivity(二)

看到很多书中都没有对PreferenceActivity做介绍&#xff0c;而我正好又在项目中用到&#xff0c;所以就把自己的使用的在这总结一下&#xff0c;也方便日后查找。 PerferenceActivity是什么&#xff0c;看下面的截图&#xff1a; Android系统截图&#xff08;左&#xff09; …

android设置页面之PreferenceActivity及Preference

离上篇博客刚好一周&#xff0c;希望后面会记录更多的内容&#xff0c;也算自己的Android笔记吧。 本篇主要记录一般的android设置页面PreferenceActivity的使用以及与之剪不断&#xff0c;理还乱的Preference。 &#xff08;一&#xff09;如何使用 Android系统自带的设置应用…

android中PreferencesActivity的使用(一)

在使用android手机的时候&#xff0c;尤其是在操作软件设置时&#xff0c;我们经常见到这样的界面&#xff1a; 这是怎么来实现的的呢&#xff1f;其实android已经提供了相应的类和方法&#xff0c;当进行简单数据存储时&#xff08;比如&#xff1a;软件配置参数&#xff09;a…

PreferenceActivity用法简介(二)

本测试主要是为了测试PreferenceActivity的使用&#xff0c;其中设置了播放背景音乐和开启wifi的设置&#xff0c;也就是本文要讲的 PreferenceActivity。 Android提供了放摆放的工具来定义所有的程序的首选项&#xff0c;并支持既不不许要编写代码的情况写显示这些首选项。可…

Android之PreferenceActivity

看到很多书中都没有对PreferenceActivity做介绍&#xff0c;而我在看Android Samples时无意中看见了&#xff0c;所以就稍微总结一下&#xff0c;也方便日后查找。 PerferenceActivity是什么&#xff0c;看下面的截图&#xff1a; 好了&#xff0c;我们看到Android系统本身就大…

Android PreferenceActivity使用

PreferenceActivity继承了ListActivity&#xff0c;定义Activity继承PreferenceActivity。在res目录下新建一个xml文件夹&#xff0c;接着在这个文件夹下新建一个取名为preferences.xml的File文件&#xff0c;xml中可以使用的标签(Tag)可以分为两类&#xff0c;一类是管理布局的…

PreferenceActivity定制

android很多设置界面都会使用PreferenceActivity来实现&#xff0c;但那个界面比较丑陋&#xff0c;显示开发总是满足不了要求。 可以自己实现一个&#xff0c;但是那样又会使Activity中的逻辑代码和xml布局文件过于复杂&#xff0c;远远不及PreferenceActivity来的方便快捷。…

PreferenceActivity、PreferenceFragment使用

目录 目录前言PreferenceActivity preferences_scenario_1xmlPreference Activity演示 PreferenceFragment xml布局文件Preference FragmentPreference Activity管理Fragment 适配 前言 转来转去又回到了Android&#xff0c;闲话少说&#xff0c;这里是参考Android原生的Setti…

Android PreferenceActivity的介绍

转载&#xff1a;http://blog.chinaunix.net/uid-24666775-id-351136.html 在Android中的APIdemos是中经常遇到过继承于PreferenceActivity这个类&#xff0c;紧接着就是addPreferencesFromResource(R.xml.*******);&#xff08;附&#xff1a;这个******就是一个XML文件&#…

Android开发--详解SharedPreference/PreferenceActivity

我们经常看到应用程序的设置页面&#xff0c;一般用到设置页面时&#xff0c;我们会继承自PreferenceActivity&#xff0c;它实现了SharedPreference&#xff0c;并生成相应的XML文件自动保存用户的设置&#xff0c;在设置页面中&#xff0c;每一个列表项都是一个Preference&am…

Android之PreferenceActivity 详解

看到很多书中都没有对PreferenceActivity做介绍&#xff0c;而我正好又在项目中用到&#xff0c;所以就把自己的使用的在这总结一下&#xff0c;也方便日后查找。 PerferenceActivity是什么&#xff0c;看下面的截图&#xff1a; Android系统截图&#xff08;左&#xff09; …

android的PreferenceActivity

前言 这段时间在研究android平台上的开源项目——StandupTimer&#xff0c;这是由jwood所设计的一个较为简单android应用&#xff0c;用于控制会议时间&#xff0c;类似秒表倒计时。 PreferenceActivity PreferenceActivity是android提供的对系统信息和配置进行自动保存的Activ…

用StopWatch统计耗时,比System.currentTimeMillis好用

平时项目中统计耗时都用System.currentTimeMillis&#xff0c;最近看到一个spring-StopWatch统计耗时&#xff0c;其用法简单明了&#xff0c;比传统统计耗时方法好用。 StopWatch 的内部是通过 System.nanoTime() 来计时的&#xff0c;本质和 System.currentTimeMillis() 差别…

并发慎用——System.currentTimeMillis()

好记忆不如烂笔头&#xff0c;能记下点东西&#xff0c;就记下点&#xff0c;有时间拿出来看看&#xff0c;也会发觉不一样的感受. System.currentTimeMillis()是极其常用的基础Java API&#xff0c;广泛地用来获取时间戳或测量代码执行时长等&#xff0c;在我们的印象中应该快…

System.currentTimeMillis()的性能问题以及解决方法

System.currentTimeMillis()是极其常用的基础Java API&#xff0c;广泛地用来获取时间戳或测量代码执行时长等&#xff0c;在我们的印象中应该快如闪电。但实际上在并发调用或者特别频繁调用它的情况下&#xff08;比如一个业务繁忙的接口&#xff0c;或者吞吐量大的需要取得时…

统计代码执行时间时,System.currentTimeMillis()与System.nanoTime()哪个更适合?

目录 1.nanoTime是什么&#xff1f; 2.currentTimeMillis是什么&#xff1f; 3.nanoTime与currentTimeMillis在JDK中阐述 4.nanoTime与currentTimeMillis使用对比 5.深究从OpenJDK源代码、glibc&#xff0c;一直到Linux内核 6.总结 1.nanoTime是什么&#xff1f; ns&…