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

news2024/12/22 23:03:38

   

            💓 博客主页:倔强的石头的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.coloradmin.cn/o/1885235.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能技术【机器学习】总结

文章目录 第一部分 优化第二部分 模型第一章 神经网络&#xff08;MLP, BP, CNN, GNN, and Attention&#xff09;1.1 神经网络基础1.1.1 高次非线性函数1.1.2 感知器与神经网络1.1.3 联结主义模型1.1.4 动机——为什么每个人都在谈论深度学习&#xff1f;1.1.5 背景1.1.6 神经…

【Android面试八股文】什么是ANR?如何分析和定位ANR?如何避免ANR?

文章目录 一、ANR概述二、触发ANR的主要场景三、Android四大组件中的潜在的ANR风险五、避免ANR的实践建议六、ANR的产生原因与出现的场景6.1 原因:6.2 出现场景:七、ANR的定位与分析7.1. ANR分析思路——traces7.2 ANR其他分析思路与相关日志7.2.1 分析logcat思路7.2.2 分析k…

机器学习:分类模型的评估指标

在机器学习中&#xff0c;评估分类模型的性能是至关重要的环节。选择合适的评估指标能够帮助我们全面了解模型的表现&#xff0c;尤其是在不同的数据分布和应用场景下。下面详细介绍一些常用的分类模型评估指标。 混淆矩阵&#xff08;confusion matrix&#xff09;是模式识别…

【Linux】TCP协议【下二】{流量控制/滑动窗口/延迟应答/捎带应答/拥塞控制}

文章目录 1.流量控制--利用“窗口大小”字段协商数据量大小1. 1第一次的时候&#xff0c;怎么保证发送数据量是合理的1.2第三次握手ack的时候&#xff0c;可以携带数据&#xff01;1.3流量控制&#xff0c;属于可靠性还是属于效率&#xff1f; 2.滑动窗口--利用滑动窗口解决批量…

Apple Intelligence或将登陆Vision Pro,但不会在今年实现|TodayAI

在上个月的WWDC 2024主题演讲中&#xff0c;Apple公司发布了其全新的Apple Intelligence&#xff0c;但没有提及这一技术将应用于其最新产品之一——Apple Vision Pro。 Apple Intelligence是iPhone制造商为应对消费者硬件中日益增长的人工智能技术需求和竞争而推出的解决方案…

【高性能服务器】多线程并发模型

&#x1f525;博客主页&#xff1a; 我要成为C领域大神&#x1f3a5;系列专栏&#xff1a;【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 ​​ 对于常见的C/…

在CenteOs7上安装mysql8.0(Super详细版)

在CenteOs7上安装mysql8.0 为什么用Mysql8.0&#xff1f;如何下载下载地址需要提前准备下载步骤 服务器上安装如何上传到服务器&#xff1f;通过wget下载到服务器并解压 开始安装非必须安装如果全部安装执行顺序 安装完后&#xff0c;启动mysql使用“systemctl”检测mysqld服务…

前端基础:JavaScript(篇一)

目录 JavaScript概述 JavaScript历史&#xff1a; 须知&#xff1a; 基本语法 变量 代码 运行 数据类型 1、数值型(number)&#xff1a; 代码 运行 2、布尔型(boolean)&#xff1a; 代码 运行 3、字符串型&#xff1a; 代码 运行 4、 undefined类型 代码…

Boss直聘,无良厂商,乱封号

耽误招工作&#xff0c;瞎吉儿封号 哥们单身 需要女生多的公司 问一下都不行&#xff0c;什么尿性 直接就给你封了 装什么呢 辣鸡boss 倒闭吧赶紧 我是狗子&#xff0c;希望你倒闭&#xff01;

GPON-GPON帧链路层知识学习

前言&#xff1a; 引用&#xff1a; gpon学习_gpon帧结构-CSDN博客 了解 GPON 技术 - Cisco GPON、XG(S)-PON基础_网络_门牙会稍息-开放原子开发者工作坊 gpon学习_gpon帧结构-CSDN博客 广域网宽带接入技术七GPON技术_gtc帧-CSDN博客 https://www.cnblogs.com/aliyunyun/…

顶顶通语音信箱手机助手拦截方案

在电话自动外呼系统&#xff0c;常见的问题是被叫号码开通了语音信箱&#xff0c;或者运营商自动给开通了小秘书服务&#xff0c;一旦电话打不通&#xff0c;就会先播放一个类似这样的提示音&#xff0c;你拨打的电话已经开启了来电小秘书&#xff0c;请在滴声后留言。还有一个…

什么是智能仓储

智能仓储是指利用先进的信息技术、物联网技术以及自动化设备&#xff0c;实现仓储管理过程的智能化&#xff0c;通过集成多种现代技术&#xff0c;使得仓库管理更加高效、精准和透明。以下是智能仓储的一些关键特点和技术&#xff1a; --------------------------------------…

夸克网盘拉新暑期大涨价!官方授权渠道流程揭秘

夸克网盘拉新暑期活动来袭&#xff0c;价格大涨&#xff01;从7月1日开始持续两个月&#xff0c;在这两个月里夸克网盘拉新的移动端用户&#xff0c;一个从原来的5元涨到了10元。这对做夸克网盘拉新的朋友来说&#xff0c;真的是福利的。趁着暑期时间多&#xff0c;如果有想做夸…

迅为2K1000核心板商业级/工业级/全国产

硬件配置 国产龙芯处理器&#xff0c;双核64位系统&#xff0c;板载2G DDR3内存&#xff0c;流畅运行Busybox、Buildroot、Loognix、QT5.12 系统! 接口全板载4路USB HOST、2路千兆以太网、2路UART、2路CAN总线、Mini PCIE、SATA固态盘接口、4G接口、GPS接口WIF1、蓝牙、Mini HD…

中原汉族与北方游牧民族舞蹈文化在这段剧中表现得淋漓尽致,且看!

中原汉族与北方游牧民族舞蹈文化在这段剧中表现得淋漓尽致&#xff0c;且看&#xff01; 《神探狄仁杰》之使团喋血记是一部深入人心的历史侦探剧&#xff0c;不仅以其曲折离奇的案情和狄仁杰的睿智形象吸引观众&#xff0c;更以其对唐代文化的精准再现而备受赞誉。#李秘书讲写…

大数据、人工智能、云计算、物联网、区块链序言【大数据导论】

各位大佬好 &#xff0c;这里是阿川的博客&#xff0c;祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 本篇序言前 必看 【大数据导论】—大数据序言 这是…

自动编码器简单理解及简单使用描述

1. 什么是自动编码器&#xff1f; 自动编码器分为编码器和解码器&#xff0c;其中解码器只在训练阶段用到。具体过程就是&#xff1a; 首先&#xff0c;输入训练样本&#xff0c;编码器对输入样本进行编码&#xff0c;对其进行降维&#xff0c;直到到达某个瓶颈层&#xff1b…

财务RPA工具——探索用于财务自动化的常见RPA工具

近年来数字化浪潮正席卷各大行业&#xff0c;数字化转型成为企业通向成功的一条必经之路。而财务管理作为企业经营过程中的有力支撑&#xff0c;是企业数字化转型的一个关键切入点。如今越来越多的数字技术在现代财务工作中得以应用&#xff0c;以提升财务工作效率&#xff0c;…

JavaScript——对象的创建

目录 任务描述 相关知识 对象的定义 对象字面量 通过关键字new创建对象 通过工厂方法创建对象 使用构造函数创建对象 使用原型(prototype)创建对象 编程要求 任务描述 本关任务&#xff1a;创建你的第一个 JavaScript 对象。 相关知识 JavaScript 是一种基于对象&a…

JVM线上监控环境搭建Grafana+Prometheus+Micrometer

架构图 一: SpringBoot自带监控Actuator SpringBoot自带监控功能Actuator&#xff0c;可以帮助实现对程序内部运行情况监控&#xff0c;比如监控内存状况、CPU、Bean加载情况、配置属性、日志信息、线程情况等。 使用步骤&#xff1a; 1. 导入依赖坐标 <dependency><…