【数据结构与算法】堆排序算法原理与实现:基于堆实现的高效排序算法

   

            💓 博客主页:倔强的石头的CSDN主页 

           📝Gitee主页:倔强的石头的gitee主页

            ⏩ 文章专栏:《数据结构与算法》

                                  期待您的关注

 

1b7335aca73b41609b7f05d1d366f476.gif

 

目录

一、引言

堆排序的简介

堆排序的特点

二、堆的概念

三、堆排序算法的原理

四、堆排序的步骤

🍃构建堆 

🍃交换与调整

🍃重复过程

五、堆排序的性能分析

🍃时间复杂度:

🍃空间复杂度:

六、示例代码

七、总结


 

一、引言

堆排序的简介

堆排序(Heap Sort)是一种基于堆数据结构实现的排序算法。利用堆这种数据结构的高效性,通过构建和调整堆来实现排序,是一种性能优秀的排序算法。

堆排序的特点

  1. 时间复杂度:堆排序的最坏、最好、平均时间复杂度均为O(nlogn),其中n是待排序元素的数量。
  2. 稳定性:堆排序在排序过程中相等的元素不会交换位置,因此它是稳定的排序算法。
  3. 选择排序:堆排序是一种选择排序,它总是选择当前未排序部分的最大(或最小)元素进行排序。

 

二、堆的概念

关于堆的详细介绍,参考前置文章

【数据结构与算法】探索数组在堆数据结构中的妙用:从原理到实现-CSDN博客

 

三、堆排序算法的原理

  • 堆排序的基本思想是将待排序的序列构建成一个堆,然后依次将堆顶元素与堆尾元素交换,并将堆的大小减小1,再对剩余的堆进行调整,使其满足堆的性质。
  • 重复这个过程,直到堆的大小为1,此时排序完成。 

四、堆排序的步骤

🍃构建堆 

借助建堆算法,降序建小堆,升序建大堆,可以选择向上或者向下调整算法

向上调整建堆的原理:
模仿堆的插入操作来构建堆,从第一个子结点开始,将它看做是新插入的元素,向上调整至满足堆的性质,然后依次往后走,直到最后一个叶子节点完成上述调整

向下调整建堆的原理:
从最后一个父结点开始,先保证它和左右子树成为堆,然后依次往前走,保证每个父结点与左右子树成堆,直到最后根结点与左右子树成堆

 

关于建堆的向上调整算法和向下调整算法有时间复杂度推导

限于篇幅,这里就不展开推导了,直接给出结论

  • 向下调整建堆的时间复杂度为O(N)
  • 向上调整算法的时间复杂度为O(n*logN)

向下调整算法优于向上调整,所以应该选择向下调整算法

 这里分别给出向下调整建小堆和向下调整建大堆的算法

(如果关于向上调整算法和向下调整算法有疑惑,建议了解完堆的这篇文章之后再来看

【数据结构与算法】探索数组在堆数据结构中的妙用:从原理到实现-CSDN博客)

void Adjustdownsmall(DataType* a, int parent, int size)//向下调整建小堆算法
{
	
	int child = parent * 2 + 1;//先假定左孩子小
	while (child < size)//循环条件是未调整至叶子节点
	{
		if (child + 1 < size && a[child + 1] < a[child])//如果右孩子存在且更小,改变为右孩子
			child++;
		if (a[child] < a[parent])//如果子节点小于父节点,交换位置
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}
void Adjustdownbig(DataType* a, int parent, int size)//向下调整建大堆算法
{
	
	int child = parent * 2 + 1;//先假定左孩子大
	while (child < size)//循环条件是未调整至叶子节点
	{
		if (child + 1 < size && a[child + 1] > a[child])//如果右孩子存在且更大,改变为右孩子
			child++;
		if (a[child] > a[parent])//如果子节点大于父节点,交换位置
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}


 

🍃交换与调整

建堆之后,就是排序
以降序为例,每次将堆顶与堆尾数据交换(相当于将当前的最小值挪到最后),然后堆尾数据伪删除(有效数据个数--,不是真删除)进行一轮向下调整,恢复堆的结构

 

Swap(&a[0], &a[end]);//交换堆顶和堆尾数据
end--;
Adjustdownsmall(a, 0, end);//向下调整恢复堆的结构

 9b7e5cb844404312a312e019bae3b8bd.png

 

🍃重复过程

 重复上述交换与调整的过程,直到堆的大小为1,此时排序完成。

 

五、堆排序的性能分析

🍃时间复杂度:

  1. 建堆:对于长度为n的数组,建堆的时间复杂度为O(n)。这是因为建堆的过程中,元素需要逐个从数组尾部加入到堆中,并重新调整堆的结构以维持其性质。每个元素加入堆中最多会触发从该元素到根节点的路径上元素的重新调整,因此,平均而言,每个元素会触发O(log n)次调整。所以,建堆的总时间复杂度为O(n *log n)。但是,由于建堆的过程是线性的(从最后一个非叶子节点开始,逐个向上调整),所以实际的时间复杂度为O(n)。
  2. 排序:在排序阶段,每次从堆顶取出最大(或最小)元素,并重新调整堆结构的时间复杂度为O(log n)。因为需要排序n个元素,所以排序阶段的时间复杂度为O(n *log n)。

综上,堆排序的总时间复杂度为O(n) + O(n *log n) = O(n *log n)。

 

🍃空间复杂度:

堆排序的空间复杂度为O(1)。这是因为堆排序是原地排序算法,它只需要常数个额外的空间来存储临时变量,而不需要额外的存储空间来存储待排序的数组。所有的操作都是直接在原数组上进行的。所以,堆排序的空间复杂度非常低。

六、示例代码

(分别给出了完整的降序排序算法和升序排序算法)

#include<stdio.h>
#include<stdlib.h>

#if 1
//堆排序
typedef int DataType;
void Swap(DataType* a, DataType* b)
{
	DataType tmp = *a;
	*a = *b;
	*b = tmp;
}
void Adjustdownsmall(DataType* a, int parent, int size)//向下调整建小堆算法
{
	
	int child = parent * 2 + 1;//先假定左孩子小
	while (child < size)//循环条件是未调整至叶子节点
	{
		if (child + 1 < size && a[child + 1] < a[child])//如果右孩子存在且更小,改变为右孩子
			child++;
		if (a[child] < a[parent])//如果子节点小于父节点,交换位置
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}
void Adjustdownbig(DataType* a, int parent, int size)//向下调整建大堆算法
{
	
	int child = parent * 2 + 1;//先假定左孩子大
	while (child < size)//循环条件是未调整至叶子节点
	{
		if (child + 1 < size && a[child + 1] > a[child])//如果右孩子存在且更大,改变为右孩子
			child++;
		if (a[child] > a[parent])//如果子节点大于父节点,交换位置
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}

void HeapSortDOrder(DataType* a,int size)//降序排序
{
	
	//向下调整建小堆
	for (int i = (size - 2) / 2; i >= 0; i--)//从最后一个父节点调整
	{
		Adjustdownsmall(a, i, size);
	}
	int end = size - 1;
	while (end>0)
	{
		Swap(&a[0], &a[end]);//交换堆顶和堆尾数据
		end--;
		Adjustdownsmall(a, 0, end);//向下调整恢复堆的结构
	}
}
void HeapSortAOrder(DataType* a, int size)//升序排序
{

	//向下调整建大堆
	for (int i = (size - 2) / 2; i >= 0; i--)//从最后一个父节点调整
	{
		Adjustdownbig(a, i, size);
	}
	int end = size - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);//交换堆顶和堆尾数据
		end--;
		Adjustdownbig(a, 0, end);//向下调整恢复堆的结构
	}
}
#endif

void print(DataType* a, int size)
{
	for (int i = 0; i < size; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
int main()
{
	int arr[] = { 12,3,5,78,46,15,23,19,20,36,52 };
	int size = sizeof(arr) / sizeof(arr[0]);
	HeapSortDOrder(arr, size);//降序
	print(arr, size);
	HeapSortAOrder(arr, size);//升序
	print(arr, size);

}

 27c88bb4be3d4b43beb24363a0fd02ae.png

七、总结

堆排序算法是一种高效且实用的排序算法,它通过利用堆数据结构的特点和性质,实现了对数据的高效排序。在实际应用中,我们可以根据问题的特点选择使用堆排序算法,以提高程序的性能和效率。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/764028.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 9.0 悄悄上线,支持面向AI的向量数据库

MySQL狂热粉丝群已经发现MySQL官网上MySQL9.0这两天悄然上线&#xff0c;已经可以下载体验了&#xff0c;目前被定义为创新版本&#xff08;Innovation&#xff09;。 下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 支持主流的操作系统&#xff0c;安装后可以直…

H5漂流瓶交友源码_社交漂流瓶H5源码

简介&#xff1a; 一种流行的娱乐性社交新潮流&#xff0c;年轻人玩得比较多。和盲盒有点类似 社交漂流瓶搭建教程 环境&#xff1a;Nginx 1.20.1-MySQL 5.6.50-PHP-7.3 上传源码至网站根目录&#xff0c;创建并导入数据库 数据库信息修改&#xff1a;/config/database.ph…

TCP 的安全可靠

TCP的安全可靠 重传机制往返时间测量快速重传 流量控制拥塞控制 重传机制 T C P确认从另一端收到的数据以提供可靠的运输层&#xff0c;但数据和确认都有可能会丢失。 T C P通过在发送时设置一个定时器来解决这种问题。如果当定时器溢出时还没有收到确认&#xff0c;它就重传该…

7.2.SQL注入-基于函数报错extractvalue(),floor()

注入基于函数报错extractvalue(),floor()-字符型 基于extractvalue() 爆出数据库版本payload语句&#xff1a; kobe and extractvalue(0,concat(0x7e,version()))#爆出数据库版本 基于floor() floor()函数就是取整数 爆出数据版本信息 kobe and (select 2 from (select …

深度解密Spark性能优化之道

课程介绍 课程通过实战案例解析和性能调优技巧的讲解&#xff0c;帮助学员提升大数据处理系统的性能和效率。课程内容涵盖了Spark性能调优的各个方面&#xff0c;包括内存管理、并行度设置、数据倾斜处理、Shuffle调优、资源配置等关键技术和策略。学员将通过实际案例的演示和…

【Altium】如何处理PCB上所有焊盘被误盖油

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 解决焊盘被误盖油的操作 2、 问题场景 所有焊盘都可以设置为盖油或不盖油&#xff0c;由于焊盘需要用来焊接元器件&#xff0c;所以都不会设置盖油。由于误操作或者创建封装时设置错误&#xff0c;造成一定数量的焊盘…

web基础及http协议

一、WEB&#xff1a;就是我们所说的页面&#xff0c;点开的每个页面都是web。&#xff08;全球广域网、万维网&#xff09; 分布式图形信息系统&#xff1a;同一个服务&#xff0c;但是部署在不同的机器上且提供的服务和内容全部一致&#xff0c;集群就是建立在分布式的基础上。…

爬虫逆向实战(42)-某巢登陆(AES、MD5、RSA、滑块验证码)

一、数据接口分析 主页地址&#xff1a;某巢 1、抓包 通过抓包可以发现在登录时&#xff0c;网站首先请求captcha/querySlideImage/来获取滑块验证码的图片&#xff0c;然后请求captcha/checkCode/接口来验证滑块验证码。滑块验证码校验成功后&#xff0c;请求noshiro/getPu…

nlp--最大匹配分词(计算召回率)

最大匹配算法是一种常见的中文分词算法&#xff0c;其核心思想是从左向右取词&#xff0c;以词典中最长的词为优先匹配。这里我将为你展示一个简单的最大匹配分词算法的实现&#xff0c;并结合输入任意句子、显示分词结果以及计算分词召回率。 代码 : # happy coding…

Ubuntu24.04之安装KVM(二百五十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

2-18 基于matlab的关于联合对角化盲源分离算法的二阶盲识别(SOBI)算法

基于matlab的关于联合对角化盲源分离算法的二阶盲识别&#xff08;SOBI&#xff09;算法。通过联合对角化逼近解混矩阵。构建的四组信号&#xff0c;并通过认为设置添加噪声比例&#xff0c;掩盖信号信息。通过SOBI算法实现了解混。程序已调通&#xff0c;可直接运行。 2-18联合…

JavaScript中location对象的主要属性和方法

属性 href&#xff1a;获取或设置整个URL。protocol&#xff1a;获取URL的协议部分&#xff0c;如"http:"或"https:"。host&#xff1a;获取URL的主机名&#xff08;包括端口号&#xff0c;如果有的话&#xff09;。hostname&#xff1a;获取URL的主机名&…

netlink通信——读取路由表获取通信网卡IP

读取路由表获取通信网卡IP是什么意思呢&#xff1f;且听我一一道来… 下面是我虚拟机两个网卡的IP&#xff0c;很明显两个网卡是不同网段的&#xff0c;我的物理机网卡网段是192.168.1.0/24&#xff0c;与我物理机和外网通信的网卡是ens160&#xff0c;即192.168.31.0/24网段&a…

2018年全国大学生数学建模竞赛A题高压油管的压力控制(含word论文和源代码资源)

文章目录 一、部分题目二、部分论文三、部分源代码问题1&#xff08;1&#xff09;绘制弹性模量与压力函数图&#xff08;2&#xff09;求最优单次开阀时间 问题二&#xff08;1&#xff09;极径与极角关系&#xff08;2&#xff09;求最优凸轮角速度 四、完整word版论文和源代…

多语言模型(Multilingual Models)用于推理(Inference)

在深入探讨多语言模型&#xff08;Multilingual Models&#xff09;用于推理&#xff08;Inference&#xff09;的详细内容时&#xff0c;我们需要首先理解多语言模型的基本概念、它们如何工作、为什么它们在现代自然语言处理&#xff08;NLP&#xff09;中变得如此重要&#x…

物理建模的一个重要概念:因果/非因果建模

物理系统的建模仿真&#xff0c;根据建模思想可划分为&#xff1a; 因果建模&#xff08;Causal Modeling&#xff09;非因果建模&#xff08;Acausal Modeling&#xff09; 二者的核心思想是通过信号流还是方程来定义模型的行为。 像我们熟知的Simulink就是基于因果建模的思…

【C++知识点总结全系列 (05)】:IO 类的详细总结和分析

1、基类 istream 和 ostream (1)istream A.What 输入流的抽象类&#xff0c;是所有输入流类的基类 B.Why&#xff08;输入流的作用&#xff09; 用于从数据源&#xff08;如文件、标准输入设备等&#xff09;读取数据 (2)ostream A.What 输出流的抽象类&#xff0c;是所有输…

Vue组件间通信方式超详细(父传子、父传后代、子传父、后代传父、兄弟组件传值、没有关系的组件传值)

Vue组件间通信方式超详细(父传子、父传后代、子传父、后代传父、兄弟组件传值)_vue 父传子-CSDN博客 vue 组件间传值&#xff1a;父传子 / 子传父 / 子传子 / 祖传孙 - 简书

RFID无线测温技术在数据中心管理中的革新与应用。

在现代信息技术飞速发展的背景下&#xff0c;数据中心作为承载企业、集团、机构核心业务的关键设施&#xff0c;其可靠性要求极高。随着大数据、云计算等技术的应用日益普及&#xff0c;数据中心面临着前所未有的挑战和机遇。其中&#xff0c;RFID无线测温技术作为一种新兴的智…

喜报 | 极限科技获得北京市“创新型”中小企业资格认证

2024年6月20日&#xff0c;北京市经济和信息化局正式发布《关于对2024年度4月份北京市创新型中小企业名单进行公告的通知》&#xff0c;极限数据&#xff08;北京&#xff09;科技有限公司凭借其出色的创新能力和卓越的企业实力&#xff0c;成功获得“北京市创新型中小企业”的…
最新文章