堆排序-程序员宅基地

技术标签: 算法  排序  

1、首先了解堆是什么

堆是一种数据结构,一种叫做完全二叉树的数据结构。

2、堆的性质

这里我们用到两种堆,其实也算是一种。

大顶堆:每个节点的值都大于或者等于它的左右子节点的值。

小顶堆:每个节点的值都小于或者等于它的左右子节点的值。

如上所示,就是两种堆。

如果我们把这种逻辑结构映射到数组中,就是下边这样

9 5 8 2 3 4 7 1
1 3 5 4 2 8 9 7

这个数组arr逻辑上就是一个堆。

从这里我们可以得出以下性质(重点)

对于大顶堆:arr[i] >= arr[2i + 1] && arr[i] >= arr[2i + 2]

对于小顶堆:arr[i] <= arr[2i + 1] && arr[i] <= arr[2i + 2]

3、堆排序的基本思想

了解了以上内容,我们可以开始探究堆排序的基本思想了。

堆排序的基本思想是:1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。

假设给定的无序序列arr是:

4 5 8 2 3 9 7 1

1、将无序序列构建成一个大顶堆。

首先我们将现在的无序序列看成一个堆结构,一个没有规则的二叉树,将序列里的值按照从上往下,从左到右依次填充到二叉树中。

 

根据大顶堆的性质,每个节点的值都大于或者等于它的左右子节点的值。所以我们需要找到所有包含子节点的节点,也就是非叶子节点,然后调整他们的父子关系,非叶子节点遍历的顺序应该是从下往上,这比从上往下的顺序遍历次数少很多,因为,大顶堆的性质要求父节点的值要大于或者等于子节点的值,如果从上往下遍历,当某个节点即是父节点又是子节点并且它的子节点仍然有子节点的时候,因为子节点还没有遍历到,所以子节点不符合大顶堆性质,当子节点调整后,必然会影响其父节点需要二次调整。但是从下往上的方式不需要考虑父节点,因为当前节点调整完之后,当前节点必然比它的所有子节点都大,所以,只会影响到子节点二次调整。相比之下,从下往上的遍历方式比从上往下的方式少了父节点的二次调整。

那么,该如何知道最后一个非叶子节点的位置,也就是索引值?

对于一个完全二叉树,在填满的情况下(非叶子节点都有两个子节点),每一层的元素个数是上一层的二倍,根节点数量是1,所以最后一层的节点数量,一定是之前所有层节点总数+1,所以,我们能找到最后一层的第一个节点的索引,即节点总数/2(根节点索引为0),这也就是第一个叶子节点,所以第一个非叶子节点的索引就是第一个叶子结点的索引-1。那么对于填不满的二叉树呢?这个计算方式仍然适用,当我们从上往下,从左往右填充二叉树的过程中,第一个叶子节点,一定是序列长度/2,所以第一个非叶子节点的索引就是arr.length / 2 -1。

现在找到了最后一个非叶子节点,即元素值为2的节点,比较它的左右节点的值,是否比他大,如果大就换位置。这里因为1<2,所以,不需要任何操作,继续比较下一个,即元素值为8的节点,它的左节点值为9比它本身大,所以需要交换

交换后的序列为:

4 5 9 2 3 8 7 1

因为元素8没有子节点,所以继续比较下一个非叶子节点,元素值为5的节点,它的两个子节点值都比本身小,不需要调整;然后是元素值为4的节点,也就是根节点,因为9>4,所以需要调整位置

交换后的序列为:

9 5 4 2 3 8 7 1

此时,原来元素值为9的节点值变成4了,而且它本身有两个子节点,所以,这时需要再次调整该节点

交换后的序列为:

9 5 8 2 3 4 7 1

到此,大顶堆就构建完毕了。满足大顶堆的性质。

2、排序序列,将堆顶的元素值和尾部的元素交换

交换后的序列为:

1 5 8 2 3 4 7 9

然后将剩余的元素重新构建大顶堆,其实就是调整根节点以及其调整后影响的子节点,因为其他节点之前已经满足大顶堆性质。

交换后的序列为:

8 5 7 2 3 4 1 9

然后,继续交换,堆顶节点元素值为8与当前尾部节点元素值为1的进行交换

交换后的序列为:

1 5 7 2 3 4 8 9

重新构建大顶堆

交换后的序列为:

7 5 4 2 3 1 8 9

继续交换

交换后的序列为:

1 5 4 2 3 7 8 9

重新构建大顶堆

构建后的序列为:

5 3 4 2 1 7 8 9

继续交换

交换后的序列为:

1 3 4 2 5 7 8 9

重新构建大顶堆

构建后的序列为:

4 3 1 2 5 7 8 9

继续交换

交换后的序列为:

2 3 1 4 5 7 8 9

重新构建大顶堆

构建后的序列为:

3 2 1 4 5 7 8 9

继续交换

交换后的序列为:

1 2 3 4 5 7 8 9

重新构建大顶堆

构建后的序列为:

2 1 3 4 5 7 8 9

继续交换

交换后的序列为:

1 2 3 4 5 7 8 9

此时,序列排序完成。以上就是整个堆排序的逻辑。

4、堆排序的代码实现(java版本)

public class HeapSort {

	public static void heapSort(int[] arr) {
		if (arr == null || arr.length == 0) {
			return;
		}
		int len = arr.length;
		// 构建大顶堆,这里其实就是把待排序序列,变成一个大顶堆结构的数组
		buildMaxHeap(arr, len);

		// 交换堆顶和当前末尾的节点,重置大顶堆
		for (int i = len - 1; i > 0; i--) {
			swap(arr, 0, i);
			len--;
			heapify(arr, 0, len);
		}
	}

	private static void buildMaxHeap(int[] arr, int len) {
		// 从最后一个非叶节点开始向前遍历,调整节点性质,使之成为大顶堆
		for (int i = (int)Math.floor(len / 2) - 1; i >= 0; i--) {
			heapify(arr, i, len);
		}
	}

	private static void heapify(int[] arr, int i, int len) {
		// 先根据堆性质,找出它左右节点的索引
		int left = 2 * i + 1;
		int right = 2 * i + 2;
		// 默认当前节点(父节点)是最大值。
		int largestIndex = i;
		if (left < len && arr[left] > arr[largestIndex]) {
			// 如果有左节点,并且左节点的值更大,更新最大值的索引
			largestIndex = left;
		}
		if (right < len && arr[right] > arr[largestIndex]) {
			// 如果有右节点,并且右节点的值更大,更新最大值的索引
			largestIndex = right;
		}

		if (largestIndex != i) {
			// 如果最大值不是当前非叶子节点的值,那么就把当前节点和最大值的子节点值互换
			swap(arr, i, largestIndex);
			// 因为互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整。
			heapify(arr, largestIndex, len);
		}
	}

	private static void swap (int[] arr, int i, int j) {
		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}
}

5、复杂度分析

因为堆排序无关乎初始序列是否已经排序已经排序的状态,始终有两部分过程,构建初始的大顶堆的过程时间复杂度为O(n),交换及重建大顶堆的过程中,需要交换n-1次,重建大顶堆的过程根据完全二叉树的性质,[log2(n-1),log2(n-2)...1]逐步递减,近似为nlogn。所以它最好和最坏的情况时间复杂度都是O(nlogn),空间复杂度O(1)。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_28063811/article/details/93034625

智能推荐

IC设计端各岗位薪酬对比(建议收藏)_模拟芯片设计中电路设计工程师和版图设计工程师薪资chayi-程序员宅基地

文章浏览阅读325次。根据人才招聘平台对于2023年已有数据统计,芯片工程师岗位均薪为26012元,位列全行业第一。不可否认,芯片行业的薪资水准确实是超越了绝大多数行业。但具体问题还需要具体分析。在参考数据时,我们也要考虑到所在地区城市、应聘者学历背景和经验、招聘企业类型的不统一性,这种不统一性会导致比较高的薪资差。1、不同学历下背景下的平均税前年薪①IC求职者硕士及以上占比96%,平均年薪为31W+;本科仅为4%,年薪平均20.5W+。②普通高校人员占比15.6%,平均年薪为26.5w+;_模拟芯片设计中电路设计工程师和版图设计工程师薪资chayi

FPGA实现Cordic算法求解arctan和sqr(x*2 + y* 2)_基于verilog的赛灵思cordic反正切fpga例程-程序员宅基地

文章浏览阅读1.4k次,点赞2次,收藏18次。由于在项目中需要使用的MPU6050,进行姿态解算,计算中设计到**arctan 和 sqr(x2 + y2),**这两部分的计算,在了解了一番之后,发现Cordic算法可以很方便的一次性求出这两个这两部分的计算。另外也可以一次性求出sin和cos的值。另外该算法还可以计算其他的一些公式(没做过多的了解)。_基于verilog的赛灵思cordic反正切fpga例程

Java动态代理类抛出java.lang.ClassCastException异常-程序员宅基地

文章浏览阅读8.2k次,点赞3次,收藏7次。Java中自带的动态代理InvocationHandler接口、Proxy类只能针对接口进行动态代理,如果要对类进行代理可以使用第三方的类库像CGLIG等相关对字节码操作实现的类库;下面我们可以看一下使用Java动态代理代理类会发生什么异常:创建一个接口类ProxyPeoplepackage com.test.Application;public class ProxyPe..._java.lang.classcastexception

javascript 文本框值变化触发事件-程序员宅基地

文章浏览阅读392次。javascript 文本框值变化触发事件jo.find(".price").bind('input onpropertychange', function () { me.calculat..._js文本框内容改变事件

C++11 tuple元组基本用法_c++取tuple中的元素-程序员宅基地

文章浏览阅读1.1k次。元组tuple是C++11的一个新特性,它是一个固定大小的不同类型值的集合,是泛化的std::pair。也可以当作一个通用的结构体来用,不需要创建结构体又获取结构体的特征,在某些情况下可以取代结构体,使程序更简洁、直观。tuple可以说是一个既简单又复杂的类型,简单的一面是很容易使用,复杂的是它内部隐藏了很多细节,往往要和模板元的一些技巧结合起来使用。_c++取tuple中的元素

Windows环境下redis重启_windowsredis怎么关闭-程序员宅基地

文章浏览阅读1.5w次,点赞4次,收藏26次。在redis安装的目录下打开cmd窗口输入以下命令打开启动redisredis-server redis.windows.conf如果提升Creating Server TCP listening socket *:6379: bind: No error,需要重启redis重启步骤:依次输入以下指令1.redis-cli -h 127.0.0.1 -p 6379 ..._windowsredis怎么关闭

随便推点

02-C语言底层驱动:NTC测温驱动_ntc.c文件-程序员宅基地

文章浏览阅读1.3k次,点赞2次,收藏10次。采用MCU超级工具计算出NTC值对应的ADC寄存器值表,不用换算成电压。ADC和NTC的参考电压只要是一样的,就能抗电压波动,不必很精准的电压就能得出比较准确的温度值。bsp_ntc.h文件:#ifndef __BSP_NTC_H#define __BSP_NTC_Hint8_t app_getNtc1Temp(void);int8_t app_getNtc2Temp(..._ntc.c文件

Springboot毕设项目考勤管理系统30gu6(java+VUE+Mybatis+Maven+Mysql)_java springboot考勤管理系统源码-程序员宅基地

文章浏览阅读64次。Jdk1.8 + Tomcat8.5 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。Springboot + mybatis + Maven + Vue 等等组成,B/S模式 + Maven管理等等。Springboot毕设项目考勤管理系统30gu6(java+VUE+Mybatis+Maven+Mysql)2. 使用IDEA/Eclipse/MyEclipse导入项目,修改配置,运行项目;_java springboot考勤管理系统源码

vba python 结合_xlwings利用VBA调用python-程序员宅基地

文章浏览阅读998次。安装配置完成后(与自定义函数UDFs的配置是一致的,加入xlwings加载项,alt+F11中引用xlwings等等常规操作后),通过 “RunPython”调用python代码。范例:VBA代码如下:Sub hi()RunPython ("import sayhi; sayhi.sayhi()") " sayhi为py文件名,sayhi()为sayhi.py中的自定义函数"End SubVBA..._vba结合python

解决iText+freemark导出pdf不支持base64的解决办法_freemark转pdf base64无法显示-程序员宅基地

文章浏览阅读1.6k次。&#13; 工具类:&#13;&#13; 1 package test;&#13; 2 &#13; 3 import java.io.IOException ;&#13; 4 import org.w3c.dom.Element ;&#13; 5 import org.xhtmlrenderer.extend.FSImage ;&#13; 6 imp..._freemark转pdf base64无法显示

pyrouge安装(Ubuntu)_pyrlug-程序员宅基地

文章浏览阅读1.4k次,点赞2次,收藏2次。一、安装rouge是一种摘要生成的自动评价指标,但python环境中的pyrouge安装并不方便,主要过程如下:1、安装Perl及依赖包(参见https://blog.csdn.net/Hay54/article/details/78744912);2、下载ROUGE-1.5.5(参见https://blog.csdn.net/Hay54/article/details/78744..._pyrlug

手撸vue菜单导航_撸导航-程序员宅基地

文章浏览阅读6.7k次。手撸菜单导航由于做官网偷懒找现成模板,但没有一个比较符合心意,于是手撸了一个效果,可自行修改用的vue模板直接上全部代码<template> <ul id="nav"> <li class="nav1"><a href="#">在线客服</a> <ul class="nav2"> <li>证劵客服</li> <li>基财客服</li_撸导航

推荐文章

热门文章

相关标签