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

news2024/11/21 0:12:47

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

「归属专栏」排序算法

「主页链接」个人主页

「笔者」枫叶先生(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
「 声明 」 余之才疏学浅,故所撰文疏漏难免,
          或有谬误或不准确之处,敬请读者批评指正。

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

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

相关文章

爬取去哪网旅游攻略信息

代码展现&#xff1a; import requests import parsel import csv import time f open(旅游去哪攻略.csv,modea,encodingutf-8,newline) csv_writer csv.writer(f) csv_writer.writerow([标题,浏览量,日期,天数,人物,人均价格,玩法]) for page in range(1,5):url fhttps://…

JS 函数

函数就是封装了一段可以被重复执行调用的代码块。目的&#xff1a;让大量代码重复利用 1、声明函数 方式一&#xff1a;利用函数关键字自定义函数&#xff08;命名函数&#xff09; function 函数名&#xff08;&#xff09;{//函数体代码} function是声明函数的关键字&#…

气膜建筑的消防安全问题如何保障?

气膜建筑作为一种独特的建筑形式&#xff0c;拥有广泛的应用领域。然而&#xff0c;由于其密闭性特点&#xff0c;人们更加关注其消防安全问题。以下是保障气膜建筑消防安全的几个关键措施&#xff1a; 采用难燃材料&#xff1a; 气膜建筑所使用的建筑膜材采用B1级难燃材料&…

Go后端开发 -- 条件、循环语句 defer语句

Go后端开发 – 条件、循环语句 && defer语句 文章目录 Go后端开发 -- 条件、循环语句 && defer语句一、条件语句1.if ... else 语句2.switch语句3.select语句 二、循环语句1.for循环 三、defer语句1.defer语句的作用2.defer和return的先后顺序3.recover错误拦截…

Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent

文章目录 Pre概述Code源码分析 Pre Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent 概述 Spring Boot 的广播机制是基于观察者模式实现的&#xff0c;它允许在 Spring 应用程序中发布和监听事件。这种机制的主要目的是为了实现解耦&#…

【JaveWeb教程】(19) MySQL数据库开发之 MySQL数据库操作-DML 详细代码示例讲解

目录 3. 数据库操作-DML3.1 增加(insert)3.2 修改(update)3.3 删除(delete)3.4 总结 3. 数据库操作-DML DML英文全称是Data Manipulation Language(数据操作语言)&#xff0c;用来对数据库中表的数据记录进行增、删、改操作。 添加数据&#xff08;INSERT&#xff09;修改数据…

第二百六十回

文章目录 知识回顾示例代码经验总结 我们在上一章回中介绍了通道相关的内容&#xff0c;本章回中将介绍其中的一种通道&#xff1a;MethodChannnel.闲话休提&#xff0c;让我们一起Talk Flutter吧。 知识回顾 我们在上一章回中介绍了通道的概念和作用&#xff0c;并且提到了通…

脱机I/O方式和假脱机系统

提示&#xff1a;在写这个博客的时候小编更加的觉得计算机基础知识的重要性了&#xff0c;而且对计算机的整个发展历程和计算机的底层工作原理特别感兴趣 脱机I/O方式和假脱机系统 一、脱机I/O方式二、假脱机系统1、假脱机技术&#xff08;SPOOLing&#xff0c; simulataneaus …

一日难再晨及时当勉励 date

文章目录 Linux shell 获取更改系统时间默认输入显示时区世界协调时格式化日期更多信息 Linux shell 获取更改系统时间 … note:: 时光只解催人老&#xff0c;不信多情&#xff0c;长恨离亭&#xff0c;泪滴春衫酒易醒。 - 晏殊《采桑子时光只解催人老》date命令可以用来打印…

GPT 商店强势来袭,人人都要有自己的 GPTs

作者&#xff1a;苍何&#xff0c;前大厂高级 Java 工程师&#xff0c;阿里云专家博主&#xff0c;CSDN 2023 年 实力新星&#xff0c;土木转码&#xff0c;现任部门技术 leader&#xff0c;专注于互联网技术分享&#xff0c;职场经验分享。 &#x1f525;热门文章推荐&#xf…

Python学习从0到1 day1 你好 Python

我会在那腥臭腐朽的日子里熠熠生辉 ——24.1.11 1.第一个Python程序 安装python程序,输出第一个程序:你好,世界 print("Hello World"); 2.Python解释器 python解释器,是一个计算机程序,用来翻译python代码,并提交给计算机执行 功能:1.翻译代码 2.提交给计算机…

快速打通 Vue 3(四):标签的 ref 属性与 Vue3 生命周期

很激动进入了 Vue 3 的学习&#xff0c;作为一个已经上线了三年多的框架&#xff0c;很多项目都开始使用 Vue 3 来编写了 这一组文章主要聚焦于 Vue 3 的新技术和新特性 如果想要学习基础的 Vue 语法可以看我专栏中的其他博客 Vue&#xff08;一&#xff09;&#xff1a;Vue 入…

【Unity】Joystick Pack摇杆插件实现锁四向操作

Joystick Pack ​ 简介&#xff1a;一款Unity摇杆插件&#xff0c;非常轻量化 ​ 摇杆移动类型&#xff1a;圆形、横向、竖向 ​ 摇杆类型&#xff1a; Joystick描述Fixed固定位置Floating浮动操纵杆从用户触碰的地方开始&#xff0c;一直固定到触碰被释放。Dynamic动态操纵…

6个Linux进程管理命令

这些命令允许你查看、监视和控制 Linux 系统上运行的进程。这对确定资源使用情况和停止行为不端的程序非常有用。 1. ps – 报告当前进程概览 使用ps&#xff0c;您可以查看当前shell会话正在运行的进程。它打印有关正在运行的程序的有用信息&#xff0c;如进程ID、TTY&#…

使用微信读书高效阅读论文,自带翻译功能。

下面以“向文本到图像扩散模型添加条件控制”&#xff08;Adding Conditional Control to Text-to-Image Diffusion Models&#xff09;这篇论文示例下阅读效果。 论文地址&#xff1a;https://arxiv.org/abs/2302.05543 选择右侧的download PDF, 然后进入论文预览页面&#x…

一、Sharding-JDBC系列01:整合SpringBoot实现分库分表,读写分离

目录 一、概述 二、案例演示-水平分表 (1)、创建springboot工程 (2)、创建数据库和数据表 (3)、application.yaml配置分片规则 (4)、测试数据插入、查询操作 4.1、插入-控制台SQL日志 4.2、查询-控制台SQL日志 三、案例演示-水平分库 (1)、创建数据库和数据表 (2…

延时任务的解决方案

延时任务的解决方案 1.数据库轮询2. JDK的延迟队列3.netty时间轮算法4.使用消息队列 1.数据库轮询 该方案通常是在小型项目中使用&#xff0c;即通过一个线程定时的去扫描数据库&#xff0c;通过订单时间来判断是否有超时的订单&#xff0c;然后进行update或delete等操作 代码示…

蓝桥杯练习题(二)

&#x1f4d1;前言 本文主要是【算法】——蓝桥杯练习题&#xff08;二&#xff09;的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 …

关于CAD导入**地球的一些问题讨论

先上示例: 上图是将北京王佐停车场的红线CAD图导入到图新地球效果,如果看官正是需要这样的效果,那么请你继续往下看,全是干货! 在地球中导入CAD图可以做为电子沙盘。对于工程人来说,是极有帮助的。以前一直用谷歌地球,大约在2020年左右,就被和谐了。当时感觉挺可惜的。…

基于OpenMV与STM32的数据通信项目(代码开源)

前言&#xff1a;本文为手把手教学 OpenMV 与 STM32 的数据通信项目教程&#xff0c;本教程使用 STM32F103C8T6 与 OpenMV 进行操作。 OpenMV 是非常强大的计算机视觉实现工具&#xff0c;自身提供了非常多的视觉项目案例&#xff0c;编程与使用门槛极低。为了进一步增强作品的…