【数据结构】堆排序详解

news2024/9/24 17:17:59

文章目录

  • 一、堆排序思想
  • 二、向上调整建堆排序
  • 三、向下调整建堆排序
  • 四、总结

对于什么是堆,堆的概念分类以及堆的向上和向下两种调整算法可见: 堆的创建

在这里插入图片描述

一、堆排序思想

int a[] = { 2,3,5,7,4,6 };

对于这样一个数组来说,要想要用堆排序对它进行排序,首先要做的就是用数组里的数据建立一个堆,大堆和小堆都可以,只有是一个堆才能使用堆排序。

那么应该建大堆还是小堆呢,例如对于这个数组要排升序,如果建立小堆的话
在这里插入图片描述

建成小堆后找出了最小的元素,要找到次小的就需要把剩下的元素看作堆,但剩下的元素不一定是堆,需要重新建堆,代价比较大。

更好的方法是升序建立大堆,堆顶和最后一个元素交换,最后一个最大的元素就已经有序,对剩下数据进行向下调整,就能找出第二大的,以此就能将数组排好序。

总结一下堆排序的思想就是:
1、根据要排什么序建大堆或小堆,此时堆顶端的元素就是最值
2、将顶端元素和末尾元素交换,此时末尾元素就是有序的,剩下的还有n-1个元素
3、将剩下的n-1个元素再次构建成堆,然后将堆顶端元素与第n-1个元素互换,反复执行便可得到有序数组

升序:建大堆
降序:建小堆

二、向上调整建堆排序

使用向上调整算法建堆的堆排序

例如:将数组a用堆排序按从小到大排列(升序)

在这里插入图片描述
首先,利用向上调整算法建大堆,此方法可参考堆的创建

向上调整算法的前提条件是:前面的元素是堆

对于单个结点来说既可以看作一个大堆,所以便可以通过向上调整算法依次对数组元素进行调整,那进行调整的元素前就一定是堆,满足条件

在这里插入图片描述

创建好的大堆如下:
在这里插入图片描述

将堆的顶端元素7和末尾元素2进行交换,对除7外剩下的元素进行向下调整重新构建大堆
在这里插入图片描述
此时7已经是有序的,将元素6和元素3进行交换,对除6、7外剩下元素进行向下调整重新构建大堆
在这里插入图片描述
此时6、7已经有序,将元素5和元素2进行交换,对除5、6、7外剩下元素进行向下调整重新构建大堆
在这里插入图片描述
此时5、6、7已经有序,将元素4和元素2进行交换,此时数组已经有序在这里插入图片描述
排序完数组a变为
在这里插入图片描述

向上调整算法建堆升序的堆排序代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int HPDataType;
//交换结点的函数
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向上调整算法(大堆)
void AdjustUp(HPDataType* a, int child)
{
	//找出双亲的下标
	int parent = (child - 1) / 2;

	while (child>0)
	{
		//孩子结点比双亲大则交换
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//向下调整算法(大堆)
void AdjustDown(HPDataType* a, int n, int parent)
{
	//先默认左孩子是较大的
	int child = parent * 2 + 1;

	while (child < n)
	{
		//找出左右孩子中较大的
		if (child + 1 < n && 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 HeapSort(int* a, int n)
{
	//向上调整建堆
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}
	//最尾端数据下标为总数减一
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		//对剩余元素进行向下调整
		AdjustDown(a, end, 0);
		end--;
	}
}
int main()
{
	int a[] = { 2,3,5,7,4,6 };
	HeapSort(a, sizeof(a) / sizeof(int));
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

运行结果如下:
在这里插入图片描述

空间复杂度:O(1)
平均时间复杂度:O(nlogn)

三、向下调整建堆排序

向下调整建堆排序与向上调整建堆排序不同的地方就在于建堆时用的算法不同,建好堆之后的后续操作都是相同的。

还是对上面那个案例,我们用向下调整算法建堆
在这里插入图片描述

向下调整算法前提条件:左右子树必须是堆,才能调整

在这里插入图片描述

对于这个完全二叉树来说,它的倒数第一个非叶子节点2的左子树为4,没有右子树,可以用向下调整,再上一个节点6的左右子树是单个节点也可以看作堆,所有我们就可以从倒数第一个非叶子节点也就是最后一个节点的父亲开始向下调整:

在这里插入图片描述

利用向下调整建好堆之后的后续操作与向上调整建好堆之后的操作一样,这里就不再演示

向下调整算法建堆升序的堆排序代码更改如下:

void HeapSort(int* a, int n)
{
	向上调整建堆
	//for (int i = 1; i < n; i++)
	//{
	//	AdjustUp(a, i);
	//}
	// 
	//向下调整建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	//最尾端数据下标为总数减一
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		//对剩余元素进行向下调整
		AdjustDown(a, end, 0);
		end--;
	}
}

利用向下调整建堆的堆排序时间复杂度为:O(n),比利用向上调整建堆更优

四、总结

使用堆排序需要先建堆,建堆有向上调整算法和向下调整算法两种方法,但向下调整算法的平均时间复杂度更低,建好堆之后便首尾数据互换,再对剩下元素重新建堆,反复执行便可得到有序数列。

重点知识总结:

  • 小堆:所有的双亲结点都小于孩子节点,根节点最小
  • 大堆:所有的双亲结点都大于孩子节点,根节点最大
  • 向下调整算法前提:左右子树必须是堆,才能调整
  • 向上调整算法前提:前面的元素是堆
  • 堆排序建堆时:升序建大堆,降序建小堆

在这里插入图片描述

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

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

相关文章

Webserver项目解析

一.webserver的组成部分 Buffer类 用于存储需要读写的数据 Channel类 存储文件描述符和相应的事件&#xff0c;当发生事件时&#xff0c;调用对应的回调函数 ChannelMap类 Channel数组&#xff0c;用于保存一系列的Channel Dispatcher 监听器&#xff0c;可以设置为epo…

分享一个基于微信小程序的社区生活小助手源码调试和lw,有java+python双版本

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人七年开发经验&#xff0c;擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等&#xff0c;大家有这一块的问题可以一起交流&#xff01; &#x1f495;&…

让NPU跑起来迅为RK3588开发板设置交叉编译器

让NPU跑起来迅为RK3588开发板设置交叉编译器 编译器下载地址是网盘资料“iTOP-3588 开发板\02_【iTOP-RK3588 开发板】开发资料 \12_NPU 使用配套资料\03_编译所需工具\Linux”。 拷贝 gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.gz 到 Ubuntu 的/opt/tool_ch…

JavaScript中的`async`和`await`关键字的作用

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ async关键字⭐ await 关键字3. 错误处理 ⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对We…

中国汽车供应商远赴德国,中国智驾方案能否远渡重洋?

作者|Amy 编辑|德新 今年的上海车展&#xff0c;中国智能汽车的进步有目共睹&#xff0c;吸引了大批外企高管和研发人员的关注&#xff0c;甚至引发了海外车企一系列的动作和调整。 而在刚刚结束的慕尼黑车展&#xff0c;中国车企及汽车供应链把「肌肉」秀到了现代汽车起源地…

flask要点与坑

简介 Flask是一个用Python编写的Web应用程序框架&#xff0c;该框架简单易用、模块化、灵活性高。 该笔记主要记录Flask的关键要点和容易踩坑的地方 Flask 日志配置 Flask 中的自带logger模块&#xff08;也是python自带的模块&#xff09;&#xff0c;通过简单配置可以实现…

浅谈拓展欧几里得算法

1、求特解 x 0 , y 0 x_0, y_0 x0​,y0​ 普通的欧几里得算法依据是辗转相除法&#xff0c;也就是&#xff0c;比如求 a &#xff0c; b a&#xff0c;b a&#xff0c;b 的最大公约数&#xff0c; a &#xff0c; b a&#xff0c;b a&#xff0c;b 进行辗转相除直到 a − b …

复盘:细数这些年写文字的成与败

引言 最近一直在思考和复盘&#xff0c;要说我这些年最后悔没坚持或者没做对的一件事就是没有好好写文字。时间过得很快&#xff0c;一晃笔者已经快毕业十年了&#xff0c;文章写得比较密集的时候还是大学时代和毕业头几年&#xff0c;后面输出就越来越少了&#xff0c;甚至一…

前端性能优化:提升网站速度与用户体验的终极指南

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 在今天的数字时代&#…

【C++ 学习 ㉑】- 详解 map 和 set(上)

目录 一、C STL 关联式容器 二、pair 类模板 三、set 3.1 - set 的基本介绍 3.2 - set 的成员函数 3.1.1 - 构造函数 3.1.2 - 迭代器 3.1.3 - 修改操作 3.1.4 - 其他操作 四、map 4.1 - map 的基本介绍 4.2 - map 的成员函数 4.2.1 - 迭代器 4.2.2 - operator[] …

避免使用违规词,企业新媒体营销应注重品牌形象维护

随着越来越多的主体入局新媒体平台&#xff0c;为了维护平台健康的内容生态和创造良好的社区氛围&#xff0c;社交平台在内容上的监管越发严格。 不可避免的&#xff0c;很多做新媒体营销的企业开始陷入与平台审核的“拉扯”之中。 为了让品牌账号在各平台上顺利运营&#xff0…

二十一、MySQL(多表)内连接、外连接、自连接实现

1、多表查询 &#xff08;1&#xff09;基础概念&#xff1a; &#xff08;2&#xff09;多表查询的分类&#xff1a; 2、内连接 &#xff08;1&#xff09;基础概念&#xff1a; &#xff08;2&#xff09;隐式内连接&#xff1a; 基础语法&#xff1a; select 表1.name,…

使用Oracle自带SqlPlus导入导出数据库脚本

sqlplus sys/passwordorcl as sysdba ----cmd 进入Oracle sqlplus 1、导入例子&#xff1a; imp username/username127.0.0.1:1521/orcl fileD:\datasource\username0919.dmp fully imp 用户名/密码127.0.0.1:1521/orcl fileD:\datasource\备份名字.dmp fully 2、导出例子&a…

Redis实战:在CentOS 7上安装部署与应用探索

PS&#xff1a;文章最后有“开心一刻”&#xff0c;记得看哦&#xff0c;给生活增加点儿趣味。 一、Redis初识 Redis&#xff0c;全称Remote Dictionary Server&#xff0c;是一个开源的键值对存储数据库。它支持多种数据类型&#xff0c;如字符串、列表、集合、有序集合、哈希…

SpringBoot-接口幂等性

幂等 幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。 幂等函数或幂等方法是指可以使用相同参数重复执行&#xff0c;并能获得相同结果的函数。这些函数不会影响系统状态&#xff0c;也不用担心重复执行会对系统造成改变。 尤其是支付、订单等与金钱挂…

进程创建fork函数

#include <sys/types.h> #include <unistd.h> pid_t fork(void); 函数的作用&#xff1a;用于创建子进程。 返回值&#xff1a; fork()的返回值会返回两次。一次是在父进程&#xff0c;一次是在子进程。 父进程中&#xff1a;返回创建的子进程的ID&#xff0c;返回…

ITIL 4指导、计划和改进—沟通和组织变革管理

第6章 沟通和组织变革管理 提供IT支持的产品和服务不仅是一种操纵技术的练习&#xff0c;而且是人类的努力。服务提供的各个方面都表现得更好&#xff0c;具有良好的沟通和对人为因素的关注。问题通常可以追溯到不正确、不匹配或时间错误的信息。人们需要帮助以适应变化中的组…

卖出看跌期权策略(Short Put)

卖出看跌期权策略&#xff08;Short Put&#xff09; 看跌期权的买家有权利按照期权的行权价卖出标的资产&#xff0c;看跌期权的卖家有义务按照期权的行权价买入标的资产。通过承担按照特定价格买入标的资产的义务&#xff0c;看跌期权的卖家可以收到期权的权利金&#xff0c…

深入解析 qsort 函数(下),用冒泡排序模拟实现 qsort 函数

前言&#xff1a;对于库函数有适当了解的朋友们&#xff0c;对于 qsort 函数想必是有认知的&#xff0c;因为他可以对任意数据类型进行排序的功能属实是有点厉害的&#xff0c;本次分享&#xff0c;笔者就给大家带来 qsort 函数的全面的解读 本次知识的分享笔者分为上下俩卷文章…

LeetCode:两数之和

题目描述&#xff1a; 这是一道用暴力解法&#xff0c;逻辑十分简单、清晰的一道题&#xff0c;直接遍历数target-num[i]就行 而官方给了第二种巧妙的解法&#xff1a;运用哈希表。此法可将时间复杂度从O&#xff08;N^2&#xff09;降到O&#xff08;1&#xff09; 其思路是…