【排序算法】四、堆排序(C/C++)

「前言」文章内容是排序算法之堆排序的讲解。(所有文章已经分类好,放心食用)

「归属专栏」排序算法

「主页链接」个人主页

「笔者」枫叶先生(fy)

目录

  • 堆排序
    • 1.1 原理
    • 1.2 堆的向下调整
    • 1.3 堆排序代码实现
    • 1.3 性质总结

堆排序

1.1 原理

概念介绍

堆是一种特殊的树形数据结构,它满足以下两个性质:

  1. 堆是一棵完全二叉树
  2. 堆中每个节点的值都必须大于等于(或小于等于)其子节点的值,这样的堆称为大根堆(或小根堆)

堆排序是一种基于二叉堆数据结构的排序算法,堆排序一般都是使用数组(顺序表)的结构进行排序(顺序存储)

堆排序算法的核心就是利用堆的性质来实现排序,堆这里就不详细介绍了(在数据结构——堆中已经详细介绍)

堆排序采用的是堆的向下调整算法(为什么选这个,在数据结构——堆中已经详细介绍)

  • 堆排序想要对排序的数进行升序排序:建小根堆(小堆)
  • 想要对排序的数进行降序排序:建大根堆(大堆)
  • 小根堆:在小根堆中,任意节点的值都小于或等于其子节点的值(最小值在根节点)
  • 大根堆:在大根堆中,任意节点的值都大于或等于其子节点的值(最大值在根节点)

堆排序的构建步骤

  1. 先构建堆:将待排序的序列构建成一个大堆(或小堆)
  2. 再调整堆:将堆顶元素与堆的最后一个元素交换,并重新调整堆,使得剩余元素仍然满足堆的性质
  3. 重复步骤2,直到所有元素都排好序

注意需要注意的是排升序要建大堆,排降序建小堆

下面介绍堆的向下调整

1.2 堆的向下调整

堆向下调整算法的基本思想是将堆中的某个节点按照堆的性质向下调整,使得以该节点为根的子树重新成为一个堆。具体步骤如下:

  1. 首先确定需要调整的节点的左右子节点中的较大值(或较小值,根据堆的性质而定)
  2. 将该节点与其左右子节点中的较大值(或较小值)进行比较,如果该节点的值不符合堆的性质(小堆或大堆),则交换两者的位置
  3. 继续对交换后的节点进行向下调整,直到该节点的值符合堆的性质或者已经没有子节点可以进行比较为止

通过这样的向下调整操作,可以保持堆的性质不变,并且在插入或删除节点之后,可以快速地恢复堆的性质

例如(以小堆为例)

  1. 从根结点处开始,选出左右孩子中值较小的孩子
  2. 让小的孩子与其父亲进行比较
  3. 若小的孩子比父亲还小,则该孩子与其父亲的位置进行交换
  4. 并将原来小的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止
  5. 若小的孩子比父亲大,则不需处理了,调整完成,整个树已经是小堆

注意:向下调整算法有一个前提:左右子树必须是一个堆,才能调整

假设向下调整27,如图所示:
在这里插入图片描述
堆的向下调整算法代码:

降序:小堆

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

// 堆的向下调整(下面是小堆:降序)
void AdjustDown(int* arr, int n, int parent) // n:arr数组的大小; parent:父节点的数组下标
{
	// 左子节点下标为parent * 2 + 1; 右子节点的下标为parent * 2 + 2
	int child = parent * 2 + 1; // 假设左孩子较大
	// 
	while (child < n)
	{
		// 选出左右孩子中小的那个
		if ((child + 1 < n) && arr[child] > arr[child + 1]) // child + 1:右孩子的下标;-- 右孩子存在,并且左孩子比右孩子大
		{
			++child;
		}
		// 孩子跟父亲比较
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]); // 交换数据
			//迭代
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

:若父节点下标为parent,左子节点下标为parent * 2 + 1,右子节点的下标为parent * 2 + 2

如果要改为升序,要建大堆

修改一下,判断条件即可

// ...
if ((child + 1 < n) && arr[child] < arr[child + 1])
// ...
if (arr[child] < arr[parent])
// ...

向下调整的时间复杂度计算

  • 使用堆的向下调整算法,最坏的情况下(即一直需要交换结点),需要循环的次数为:h-1次(h为树的高度)
  • h = log(N+1)(N为树的总结点数,log以2为底)
  • 所以堆的向下调整算法的时间复杂度为:O(logN)

1.3 堆排序代码实现

前面说到,使用堆的向下调整算法需要满足其根结点的左右子树均为大堆或是小堆才行,那么如何才能将一个任意树调整为堆呢?

只需要从倒数第一个非叶子结点开始,从后往前,按下标,依次作为根去向下调整即可

// 1.建堆
// 向下调整建堆方式时间复杂度:O(N)
// 从第一个非叶子结点开始向下调整,一直到根
for (int i = (n - 1 - 1) / 2; i >= 0; --i) // n-1:数组下标上限,(n-1-1)/2:孩子的父亲节点
{
	AdjustDown(arr, n, i);
}

以建小堆为例,如图所示:

  • 第一次向下调整:(非叶子节点开始)
    在这里插入图片描述
  • 第二次向下调整:在这里插入图片描述
  • 第三次向下调整:在这里插入图片描述
  • 第四次向下调整:在这里插入图片描述
  • 第五次向下调整:(到根节点结束)在这里插入图片描述

堆排序分两步:

  1. 建堆
  2. 调整(即排序的过程)

堆排序代码如下:

//堆排序
void HeapSort(int* arr, int n) // n:数组大小
{
	// 1.建堆
	// 向下调整建堆方式时间复杂度:O(N)
	// 从第一个非叶子结点开始向下调整,一直到根
	for (int i = (n - 1 - 1) / 2; i >= 0; --i) // n-1:数组下标上限,(n-1-1)/2:孩子的父亲节点
	{
		AdjustDown(arr, n, i);
	}

	// 2.调整(排序的过程)
	// 调整时间复杂度:O(N*logN)
	int end = n - 1; // 记录堆的最后一个数据的下标
	while (end > 0)
	{
		// 堆顶数据与堆的最后一个数据交换
		Swap(&arr[0], &arr[end]);
		// 进行调整
		AdjustDown(arr, end, 0); // 对根进行一次向下调整
		end--; // 堆的最后一个数据的下标减一
	}
}

堆排序的时间复杂度计算

建堆的时间复杂度:

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果)
在这里插入图片描述
计算建堆过程中总共交换的次数:

① T ( n ) = 2 0 ∗ ( h − 1 ) + 2 1 ∗ ( h − 2 ) + 2 2 ∗ ( h − 3 ) + . . . + 2 h − 3 ∗ 2 + 2 h − 2 ∗ 1 ① T(n)=2^0*(h-1)+2^1*(h-2)+2^2*(h-3)+...+2^{h-3}*2+2^{h-2}*1 T(n)=20(h1)+21(h2)+22(h3)+...+2h32+2h21

两边同时乘2得:(等差数列乘以一个等比数列,使用裂项相消法进行化简)

② 2 T ( n ) = 2 1 ∗ ( h − 1 ) + 2 2 ∗ ( h − 2 ) + 2 2 ∗ ( h − 3 ) + . . . + 2 h − 2 ∗ 2 + 2 h − 1 ∗ 1 ②2 T(n)=2^1*(h-1)+2^2*(h-2)+2^2*(h-3)+...+2^{h-2}*2+2^{h-1}*1 ②2T(n)=21(h1)+22(h2)+22(h3)+...+2h22+2h11

②-①两式相减得:(错位相减)

T ( n ) = 1 − h + 2 1 + 2 3 + . . . + 2 h − 2 + 2 h − 1 T(n)=1-h+2^1+2^3+...+2^{h-2}+2^{h-1} T(n)=1h+21+23+...+2h2+2h1

T ( n ) = 2 0 + 2 1 + 2 3 + . . . + 2 h − 2 + 2 h − 1 − h T(n)=2^0+2^1+2^3+...+2^{h-2}+2^{h-1}-h T(n)=20+21+23+...+2h2+2h1h

运用等比数列求和得:

T ( n ) = 2 h − h − 1 T(n)=2^h-h-1 T(n)=2hh1

由二叉树的性质,有 N = 2 h − 1 N=2^h-1 N=2h1 h = l o g 2 ( N + 1 ) h=log_2(N+1) h=log2(N+1),于是:

T ( n ) = N − l o g 2 ( N + 1 ) ≈ N T(n)=N-log_2(N+1)≈N T(n)=Nlog2(N+1)N

用大O的渐进表示法,即:

T ( n ) = O ( N ) T(n)=O(N) T(n)=O(N)

因此:建堆的时间复杂度为O(N)

排序时间复杂度:

  • 每次进行堆的向上调整时间复杂度为O(logN)
  • 数组一共有n个元素
  • 进行排序的时间复杂度为:O(nlogn)

堆排序的总时间复杂度为O(n + nlogn) = O(nlogn)

1.3 性质总结

  • 时间复杂度:堆排序的平均时间复杂度为 O(nlogn),最坏情况下也为 O(nlogn)
  • 空间复杂度:O(1)
  • 不稳定性:堆排序是一种不稳定的排序算法,即相同元素的相对位置可能会发生变化
  • 适用范围:堆排序适用于大数据量的排序,对于小数据量的排序效率较低

--------------------- END ----------------------

「 作者 」 枫叶先生
「 更新 」 2024.1.11
「 声明 」 余之才疏学浅,故所撰文疏漏难免,
          或有谬误或不准确之处,敬请读者批评指正。

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

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

相关文章

Mondo备份linux操作系统为iso镜像 —— 筑梦之路

简介 Mondo Rescue&#xff08;以下简称Mondo&#xff09;可以说是Linux 下的Ghost&#xff0c;它可以将你的系统像照相一样备份至磁带&#xff0c;CD-R&#xff0c;CD-RW&#xff0c;NFS或硬盘分区。Mondo广泛支援LVM&#xff0c;RAID&#xff0c;ext2, ext3, JFS, XFS,Reise…

平时执行很快的SQL语句,为什么会突然卡一下?

InnoDB在处理更新语句的时候&#xff0c;只做了写日志这一个磁盘操作&#xff0c;这个日志叫作redo log&#xff08;重做日志&#xff09;&#xff0c;在更新内存写完redo log后&#xff0c;就返回给客户端&#xff0c;本次更新成功。 把内存里的数据写入磁盘的过程&#xff0…

烟火检测AI边缘计算智能分析网关V4在安防项目中的应用及特点

一、行业背景 随着社会和经济的发展&#xff0c;公共安全和私人安全的需求都在不断增长。人们需要更高效、更准确的安防手段来保障生命财产安全&#xff0c;而人工智能技术正好可以提供这种可能性&#xff0c;通过智能监控、人脸识别、行为分析等手段&#xff0c;大大提高了安防…

JVM初识

什么是JVM&#xff1f; JVM全称是Java Virtual Machine&#xff0c;中文译名Java虚拟机。 JVM本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行Java字节码文件。 JVM的功能 jvm的功能主要分为三部分&#xff1a; 解释和运行 对字节码文件中的指令&#xff0c;实…

【机器学习】模型调参工具:Hyperopt 使用指南

机器学习| 模型调参工具&#xff1a;Hyperopt 使用指南 前言1. Hyperopt是什么&#xff1f;2. Hyperopt的优缺点3. 如何使用 Hyperopt 进行调参3.1 安装 Hyperopt3.2 构建超参数空间3.3 定义目标函数3.4 运行 Hyperopt 优化3.5 获取最优超参数 4. XGB调参代码示例参考资料 前言…

idea编译报错(Maven项目)

idea编译报错 找不到符号 第一步&#xff1a;开启注解处理器 第二步&#xff1a;清理MVN&#xff0c;package并重新编译 第三步&#xff1a;重新导入项目&#xff1a;

SAP PP配置学习(五)

查找 四、 其它 设置 MM 过帐号码范围 定义凭证号码范围 OB52 打开期间 MMPV 开帐 &#xff08;下篇见&#xff09;

GC2003七通道NPN 达林顿管,专为符合标准 TTL 而制造

GC2003 内部集成了 7 个 NPN 达林顿晶体管&#xff0c;连接的阵列&#xff0c;非常适合逻辑接口电平数字电路&#xff08;例 如 TTL&#xff0c;CMOS 或PMOS 上/NMOS&#xff09;和较高的电流/电压&#xff0c;如电灯电磁阀&#xff0c;继电器&#xff0c;打印机或其他类似的负…

深入探讨:开发连锁餐饮APP的关键技术要点

时下&#xff0c;开发一款功能强大、用户友好的连锁餐饮APP成为许多餐饮企业的当务之急。在本文中&#xff0c;我们将深入探讨开发连锁餐饮APP的关键技术要点&#xff0c;涵盖了前端、后端以及数据库等方面。 一、前端开发 前端是用户与APP交互的入口&#xff0c;因此设计良好…

.NET开源、强大的Web报表统计系统

前言 今天分享一个.NET开源、强大的Web报表统计系统&#xff1a;CellReport。 项目官方介绍 CellReport 诞生的初衷是为了解决日常快速制作统计报表的需要。 CellReport 是一个为复杂统计报表为核心目标的制作、运行工具。你可以使用数据库、excel文件、api服务、已有报表等为…

【开源】基于JAVA语言的康复中心管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 普通用户模块2.2 护工模块2.3 管理员模块 三、系统展示四、核心代码4.1 查询康复护理4.2 新增康复训练4.3 查询房间4.4 查询来访4.5 新增用药 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的康复中…

华为“纯血”鸿蒙加速进场 高校、企业瞄准生态开发新风口

近日&#xff0c;华为终端BG CEO、智能汽车解决方案BU董事长余承东在2024年新年信中提出&#xff0c;开启华为终端未来大发展的新十年。 他特别提到&#xff0c;未来要构建强大的鸿蒙生态&#xff0c;2024年是原生鸿蒙的关键一年&#xff0c;将加快推进各类鸿蒙原生应用的开发…

【Node.js学习 day3——http模块】

创建HTTP服务端 //1.导入http模块 const http require(http);//2.创建服务对象 const server http.createServer((request, response) > {response.end(Hello HTTP Server);//设置响应体 });//3.监听端口&#xff0c;启动服务 server.listen(9000,()>{console.log(服务…

【PaperReading】5. Open-Vocabulary SAM

Category Content 论文题目 Open-Vocabulary SAM: Segment and Recognize Twenty-thousand Classes Interactively 作者 Haobo Yuan1 Xiangtai Li1 Chong Zhou1 Yining Li2 Kai Chen2 Chen Change Loy1 1S-Lab, Nanyang Technological University 2Shanghai Artificial In…

力扣日记1.11-【二叉树篇】450. 删除二叉搜索树中的节点

力扣日记&#xff1a;【二叉树篇】450. 删除二叉搜索树中的节点 日期&#xff1a;2024.1.11 参考&#xff1a;代码随想录、力扣 450. 删除二叉搜索树中的节点 题目描述 难度&#xff1a;中等 给定一个二叉搜索树的根节点 root 和一个值 key&#xff0c;删除二叉搜索树中的 key…

燃情瞬间,智能酒精壁炉点亮户外聚会新潮流

在户外聚会中&#xff0c;一种备受瞩目的装饰品和功能性家居设备正逐渐崭露头角&#xff0c;那就是智能酒精壁炉。这种独特的户外装置不仅为聚会场合带来独特的氛围&#xff0c;还具有许多引人注目的优势。 其明亮的火焰不仅照亮整个场所&#xff0c;还散发出温暖迷人的光芒&am…

创建型模式 | 工厂模式

文章目录 一、简单工厂1.1、原理1.2、核心角色1.3、UML类图1.4、代码实现1.5、总结 二、工厂模式2.1、原理2.2、关键角色2.3、代码实现2.4、总结 三、抽象工厂模式3.1、原理3.2、关键角色3.3、UML类图3.4、工厂模式与抽象工厂模式的区别 前言 工厂模式是最常用的设计模式之一&a…

知识引导的分子生成扩散模型 - KGDiff 评测

一、背景介绍 KGDiff模型是一个基于口袋的知识引导的3D分子生成的扩散模型&#xff0c;来源于上海交通大学计算机学院涂仕奎教授的文章&#xff1a; 《KGDiff: towards explainable target-aware molecule generation with knowledge guidance》。文章链接&#xff1a;*KGDiff…

Qt QTableView和QStandardItemModel包含搜索出现的文本及隐藏顶层节点

前言 使用Qt进行开发时&#xff0c;树结构一般是使用QTreeWidget或使用QTreeViewQStandardItemModel结合。 查找 如果要进行查找树的所有项中&#xff0c;是否包含某文本&#xff0c;就需要遍历。 QTreeWidget查找 以下是使用QTreeWidget进行查找&#xff1a; 首先初始化一…

跟着仙凡兄学习编译Telegram vs2022 2024.1.11编译成功

编译Telegram 本人花了两天&#xff0c;问官方作者终于编译成功Telegram 运行环境&#xff1a;win11 vs2022 参见学习视频&#xff1a;【telegram编译成功&#xff0c;编译遇到的各种问题】https://www.bilibili.com/video/BV11c411x7jm?vd_sourcedf2e51268cc7412cc3937cf3df2…