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

article/2025/10/29 22:47:28

目录

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. 堆排序的基础知识

堆排序(Heap Sort)就是对直接选择排序的一种改进。此话怎讲呢?直接选择排序在待排序的n个数中进行n-1次比较选出最大或者最小的,但是在选出最大或者最小的数后,并没有对原来的序列进行改变,这使得下一次选数时还需要对全部数据进行比较,效率大大降低。

堆排序算法是Floyd和Williams在1964年共同发明的,同时他们发明了“堆”这种数据结构。

1.1 大顶堆&&小顶堆 

对于待排序的数组元素,我们可以将它的逻辑结构看成二叉树。满足某种条件的这种逻辑结构就是堆啦。

显然,这是一棵完全二叉树。那么某种条件是啥呢?

堆是具有下列性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,称为:大顶堆(大堆);或者每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆(小堆)。 

 

1.2 向下调整算法

使用该算法的前提是该节点的左右子树要么都是大堆,要么都是小堆。

该算法就是将该节点与左右子树的节点进行比较,重新构建成一个大堆或者小堆。

1.3 物理结构与逻辑结构的关系

假设我们有了一个待排序的数组,并且构建好了他的逻辑结构,怎么能通过孩子找到双亲,或者通过双亲找到左右孩子呢?

先看公式: parent = (child - 1) / 2 ;

                   leftchild = parent * 2 + 1 ;

                   rightchild = parent * 2 + 2 ;

                   rightchild = leftchild + 1;

有了这个公式,左孩子,右孩子,双亲的下标知道一个就可以求其他两个啦!

 

2. 堆排序详解

是的,堆排序就是通过建堆的方式来弥补直接选择排序的缺陷滴。 

2.1 堆排序整体思路 

1):给出待排序的数组,咱们脑补一个逻辑结构,然后将该逻辑结构整体调整为大堆或者小堆。 

2): 留个问题:升序是构建大堆还是小堆呢?提示:请参考直接选择排序的缺陷,以及堆排序如何弥补该缺陷的。搞清楚这个问题后排序即可。

3):打印输出数据。

2.2 思路详解

2.2.1 建堆

大堆为例哈:既然前面都铺垫了向下调整算法,你们肯定猜到了是通过该算法来建堆啦。

注意该算法的使用前提:要求左右子树都是大堆。怎么办呢?聪明如你们,我们从逻辑结构的后面往前面用该算法不就行啦!!

 

/// <summary>
/// 交换数组元素
/// </summary>
/// <param name="pa">被交换的元素a</param>
/// <param name="pb">被交换的元素b</param>
void Swap(int* pa, int* pb)
{int tmp = *pa;*pa = *pb;*pb = tmp;
}/// <summary>
/// 对逻辑结构进行向下调整,构建大堆
/// </summary>
/// <param name="arr">数组首元素地址</param>
/// <param name="n">数组大小</param>
/// <param name="root">要调整的节点</param>
void AdjustDown(int* arr, int n, int root)
{//双亲的下标int parent = root;//较大孩子的下标,默认为左孩子int child = parent * 2 + 1;//如果孩子的下标不越界,进入循环while (child < n){//如果右孩子存在(下标没越界),并且右孩子大于左孩子,更新childif (child + 1 < n && arr[child + 1] > arr[child]){child = child + 1;}//如果较大的孩子大于双亲,交换if (arr[child] > arr[parent]){Swap(&arr[child], &arr[parent]);//改变parent的下标如果满足条件继续向下调整parent = child;child = parent * 2 + 1;}//如果较大的孩子不大于双亲,root节点的大堆构建完毕else{break;}}
}/// <summary>
/// 堆排序函数
/// </summary>
/// <param name="arr">数组首元素地址</param>
/// <param name="n">数组大小</param>
void HeapSort(int* arr, int n)
{//循环从后面往前面对需要的数组元素使用向下调整算法int i = 0;for (i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(arr, n, i);}
}

2.2.2 大堆or小堆

以升序为例:建大堆完成后就是这个样子啦:9, 8, 6, 7, 3, 1, 2, 4, 5, 0 ;

                      建小堆完成后就是这个样子:0, 3, 1, 5, 4, 2, 9, 7, 8, 6 ;

建小堆只需要将AdjustDown里面的两个if语句的大于改成小于。

2.2.3 输出数据

这部分比较简单,不多分析。 

3. 时间复杂度分析

堆排序的时间复杂度分析,应该是排序算法中最复杂的,需要具备高中的基础知识!!!!

 

 

4. 完整代码

#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>/// <summary>
/// 交换数组元素
/// </summary>
/// <param name="pa">被交换的元素a</param>
/// <param name="pb">被交换的元素b</param>
void Swap(int* pa, int* pb)
{int tmp = *pa;*pa = *pb;*pb = tmp;
}/// <summary>
/// 对逻辑结构进行向下调整,构建大堆
/// </summary>
/// <param name="arr">数组首元素地址</param>
/// <param name="n">数组大小</param>
/// <param name="root">要调整的节点</param>
void AdjustDown(int* arr, int n, int root)
{//双亲的下标int parent = root;//较大孩子的下标,默认为左孩子int child = parent * 2 + 1;//如果孩子的下标不越界,进入循环while (child < n){//如果右孩子存在(下标没越界),并且右孩子大于左孩子,更新childif (child + 1 < n && arr[child + 1] > arr[child]){child = child + 1;}//如果较大的孩子大于双亲,交换if (arr[child] > arr[parent]){Swap(&arr[child], &arr[parent]);//改变parent的下标如果满足条件继续向下调整parent = child;child = parent * 2 + 1;}//如果较大的孩子不大于双亲,root节点的大堆构建完毕else{break;}}
}
/// <summary>
/// 打印数组元素
/// </summary>
/// <param name="arr">数组首元素地址</param>
/// <param name="n">数组元素个数</param>
void PrintArray(int* arr, int n)
{int i = 0;for (i = 0; i < n; i++){printf("%d ", arr[i]);}
}/// <summary>
/// 堆排序函数
/// </summary>
/// <param name="arr">数组首元素地址</param>
/// <param name="n">数组大小</param>
void HeapSort(int* arr, int n)
{//循环从后面往前面对需要的数组元素使用向下调整算法int i = 0;for (i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(arr, n, i);}//用来控制向下调整时的数组元素个数int end = n - 1;while (end > 0){//交换元素,获得最大值Swap(&arr[0], &arr[end]);//进行向下调整, 因为开始end为n-1嘛AdjustDown(arr, end, 0);//去除较大值--end;}
}int main()
{//待排序的数组int arr[] = { 6,4,2,8,3,1,9,7,5,0 };//调用主体函数HeapSort(arr, sizeof(arr) / sizeof(arr[0]));//打印数据PrintArray(arr, sizeof(arr) / sizeof(arr[0]));return 0;
}

 5. 彩蛋

为了直观的比较几大排序算法的快慢,我们可以设计程序以毫秒数来量化排序的快慢。

 

 


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

相关文章

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

堆排序的时间复杂度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&…

System.currentTimeMillis的性能如何

一、背景 撸代码时发现System.currentTimeMillis的调用都被封装成了cache类型&#xff0c;代码如下: 那么System.currentTimeMillis真的有这么这么差吗&#xff0c;如果差的话又是什么原因造成的&#xff1f;什么情况下可以直接调用原生方法&#xff0c;什么情况下需要使用缓存…