【数据结构】第十八弹---C语言实现堆排序

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1、堆排序

1.1、基本思想

1.2、初步代码实现

1.3、代码优化

1.4、代码测试

总结


1、堆排序

在博主数据结构第十二弹---堆的应用有详细讲解堆排序喔~~~

数据结构第十二弹---堆的应用https://blog.csdn.net/2201_75584283/article/details/135348207icon-default.png?t=N7T8https://blog.csdn.net/2201_75584283/article/details/135348207

1.1、基本思想

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆

为什么升序用到的是大堆呢?

因为:大堆的堆顶是最大的数,可以将其放在数组尾,然后再通过向下调整算法找到次大的数。而小堆的堆顶是最小的数,需要放在第一个位置,如果用原来的堆找不到次小的数,而重新建堆则会更加繁琐。

降序用小堆同理。

动图如下:

1.2、初步代码实现

堆排序的实现可以分为两部分:构建最大堆(或最小堆)和执行排序过程。

注意:此处我们实现的是大堆!!!

第一步:建堆

我们此处是对数组内部的数进行排序,那么怎样才能创建成大堆呢?

这里我们可以使用向上调整算法,从第二个数开始遍历数组,如果不满足大堆则交换父子元素。

for (int i = 1; i < n; i++)
{
	AjustUp(a, i);
}

大堆向上调整:

  1. 将被调整的数值与其父节点比较,若是大于父节点,则与父节点交换。
  2. 若子节点下标为child,父节点下标为(child - 1) / 2。
  3. 当子节点小于父节点时,或者当子节点已经为堆顶时,停止比较。

代码实现:

void AdjustUp(int* a, int child)
{
	int parent = 0;
	while (child > 0)
	{
		parent = (child - 1) / 2;
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
		}
		else
		{
			break;
		}
	}
}

小堆向上调整:

 与向下调整大堆思想的唯一区别就是:将被调整的数值与父节点比较,若是小于父节点,则与父节点交换,将小根堆比较条件改为小于即可

if (a[child] < a[parent])//孩子小于父亲则交换
{
    ...
}

每次向上调整算法的时间复杂度为O(log N)。 

​ 所以使用向上调整建好堆的时间复杂度为(N*log N)

第二步:执行排序操作

进行了向上调整之后,此时的数组就是一个大堆了,要怎样才能达到升序呢?

  1. 使用大根堆选出最大的数,放在数组的最后一个位置,依次选出进行排序。
  2. 将堆顶和最后一个数交换。
  3. 然后将新堆顶向下调整,形成堆。
  4. 向下调整时,注意交换后的最后位置不在新堆里,所以要下标要减一。
  5. 没有对堆结构造成破坏,不用对每个数都调整。
//2.向下调整 O(N*logN)
int end = n - 1;
while (end > 0) //从最后一个位置开始交换并调整
{
	Swap(&arr[0], &arr[end]);
	AdjustDown(arr, end, 0);//此处为大根堆向下调整方式
	end--;
}

 大堆向下调整:

  1. 将被调整的数值与其最大的子节点比较,若是小于最大的子节点,则与该子节点交换。
  2. 若父节点下标为parent,左子节点下标为 parent * 2 + 1,右子节点的下标为 parent * 2 + 2。
  3. 获取最大的子节点时,可以先将左子节点作为最大节点,再与右子节点比较,若是大于右子节点,则将左子节点下标加1得到右子节点下标。
  4. 再循环体内比较左右子节点之前,要先判断右子节点存在,防止堆最后一个节点是左子节点造成越界访问。
  5. 当子节点小于父节点时,或者当子节点超过堆的范围时,停止比较。

//向下调整算法 大堆
void AdjustDown(int* a, int size, int parent)
{
	//1.假设左孩子为小的数据
	int child = parent * 2 + 1;
	while (child < size)
	{
		//2.如果左孩子>右孩子 则将右孩子赋值
		//有可能只有左孩子 所以加条件
		//以下未有左右孩子且左孩子>右孩子情况,则将child++
		if (child + 1 < size && a[child] < a[child + 1])
		{
			child++;
		}
		//3.将孩子与父亲进行比较 如果孩子小则交换
		//然后将父亲和孩子移动到下一个位置
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

小堆向下调整:

  1. 将被调整的数值与其最小的子节点比较,若是大于最小的子节点,则与该子节点交换。
  2. 将小根堆向下调整时左右子节点的比较条件和父节点与子节点的比较改为小于即可。
if (child + 1 < size && a[child] > a[child + 1])
{
	...	
}

if (a[child] < a[parent])
{
    ...
}

堆排序的代码如下:

void HeapSort(int arr[], int n)
{
	assert(arr);
	//1.建堆 向上调整 O(N*logN)
	for (int i = 1; i < size; i++)
	{
		AdjustUp(arr, i);
	}
	//2.向下调整 O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);
		AdjustDown(arr, end, 0);
		end--;
	}
}

1.3、代码优化

在建堆的时候,我们既可以使用向上调整算法建堆,也可以使用向下调整算法建堆,在堆的应用那一弹我们计算了向下调整算法建堆的时间复杂度,对整个数组进行向下调整的时间复杂度是O(N),因此我们在堆排序的时候也可以统一使用向下调整算法!!!

向下调整:

  1. 向下调整是从后往前调整,先将后面构成堆,再调整上面的节点。
  2. 以叶子节点作为根进行向下调整是完全没有必要的,叶子节点没有子节点,所以对最后一个叶子节点的父节点开始向下调整。
  3. 最后一个节点下标是n-1,它的父节点为 (n-1-1) / 2。

//1.向下调整建堆 O(N)
for (int i = (n - 1 - 1) / 2; i >= 0; i--)//从n-2 到 0 进行调整
{
	AdjustDown(arr, n, i);
}

 

堆排序代码如下:

void HeapSort(int arr[], int n)
{
	assert(arr);
	//1.向下调整建堆 O(N)
    for (int i = (n - 1 - 1) / 2; i >= 0; i--)//从n-2 到 0 进行调整
    {
	    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--;
	}
}

1.4、代码测试

测试代码:

//测试堆排序
int main()
{
	int a[] = { 9,8,7,6,5,4,3,2,1,0 };//给一组数据
	int sz = sizeof(a) / sizeof(a[0]);//计算数组元素个数
	printf("排序前:\n");
	ArrayPrint(a, sz);
	HeapSort(a, sz);
	printf("排序后:\n");
	ArrayPrint(a, sz);
	return 0;
}

测试结果:

 

堆排序的特性总结:

1. 堆排序使用堆来选数,效率就高了很多。
2. 时间复杂度:O(N*logN)。
3. 空间复杂度:O(1)。
4. 稳定性:不稳定。

5. 复杂性:复杂。

总结

本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!

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

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

相关文章

Hadoop 2.0 大家族(一)

目录 一、Hadoop 2.0大家族概述&#xff08;一&#xff09;分布式组件&#xff08;二&#xff09;部署概述 二、ZooKeeper&#xff08;一&#xff09;ZooKeeper简介&#xff08;二&#xff09;ZooKeeper 入门 一、Hadoop 2.0大家族概述 &#xff08;一&#xff09;分布式组件 …

Java中的While循环及其示例

Java中的While循环及其示例 在本教程中&#xff0c;您将借助示例在java中学习while循环。与for循环类似&#xff0c;while循环用于重复执行一组语句&#xff0c;直到指定的条件返回false。 while循环的语法 while(condition) {statement(s); //block of code } while循环的…

RAG优化技巧|7大挑战与解決方式|提高你的LLM能力

在当今快速发展的人工智能领域&#xff0c;大型语言模型&#xff08;LLM&#xff09;已经成为无处不在的技术&#xff0c;它们不仅改变了我们与机器交流的方式&#xff0c;还在各行各业中发挥着革命性的影响。 然而&#xff0c;尽管LLM RAG的能力已经让人惊叹&#xff0c;但我…

Salia PLCC cPH2 远程命令执行漏洞(CVE-2023-46359)

漏洞描述 Salia PLCC cPH2 v1.87.0 及更早版本中存在一个操作系统命令注入漏洞&#xff0c;该漏洞可能允许未经身份验证的远程攻击者通过传递给连接检查功能的特制参数在系统上执行任意命令。 产品界面 fofa语法 "Salia PLCC" POC GET /connectioncheck.php?ip1…

考研计组chap2数据的表示和运算

3一、进位计数制 1.r进制 第i位表示r进制的权为i 2.进制转换 &#xff08;1&#xff09;r->10 对应位置数*权值 &#xff08;2&#xff09;2 -> 16 or 8 每三位2进制数可表示1位16进制 每四位2进制数可表示1位16进制 so 分开之后转为16进制即可 eg&#xff1a;1…

iOS APP内存泄漏的问题

iOS APP内存泄漏是指应用程序不再使用内存&#xff0c;但内存却没有被释放&#xff0c;导致应用程序占用过多的内存&#xff0c;甚至崩溃。内存泄漏是iOS开发中常见的问题&#xff0c;会严重影响应用程序的性能和稳定性。北京木奇移动技术有限公司&#xff0c;专业的软件外包开…

【Java】BigDecimal类型——BigDecimal 为什么可以保证精度不丢失

目录 简介类介绍案例分析总结BigDecimal类型的使用场景MySQL中存储BigDecimal类型数据补充&#xff1a;BigDecimal类型使用时的注意事项BigDecimal类型的其他使用 简介 BigDecimal是Java中的一个类&#xff0c;用于处理大数运算。它提供了精确的数值计算&#xff0c;可以处理任…

PCB相关

PCB过孔过流能力计算软件&#xff1a; PCB过孔载流计算器EDA在线工具PCB联盟网 - Powered by Discuz! 孔径&#xff1a;过孔直径 温升&#xff1a;过孔温升标准 参考资料&#xff1a; PCB及钢网与嘉力创标准_嘉立创不支持盲埋孔-CSDN博客&#xff08;待学习&#xff09; PC…

openEuler系统中LVM逻辑卷的创建及扩容与缩容

1、背景说明 安装好openEuler操作系统后为其增加新的磁盘进行逻辑卷的扩容与缩容 本次测试使用VMware Workstation Pro虚拟机增加一个磁盘大小为500GB&#xff0c;虚机不关机直接加盘后&#xff0c;使用ls /dev/sd* 或者fdisk -l 发现没有新加的磁盘设备Disk /dev/sdb &#…

MAVEN-SNAPSHOT和RELEASE + 打包到远程仓库

一、快照版本SNAPSHOT和发布版本RELEASE区别 快照版本SNAPSHOT和发布版本RELEASE区别-CSDN博客 在使⽤maven过程中&#xff0c;我们在开发阶段经常性的会有很多公共库处于不稳定状态&#xff0c;随时需要修改并发布&#xff0c;可能⼀天就要发布⼀次&#xff0c;遇到bug时&am…

[面试题]Kafka

[面试题]Java【基础】[面试题]Java【虚拟机】[面试题]Java【并发】[面试题]Java【集合】[面试题]MySQL[面试题]Maven[面试题]Spring Boot[面试题]Spring Cloud[面试题]Spring MVC[面试题]Spring[面试题]MyBatis[面试题]Nginx[面试题]缓存[面试题]Redis[面试题]消息队列[面试题]…

git merge(3个模式) 与 git rebase 图文详解区别

目录 1 git merge1.1 模式一&#xff1a;fast-forward(–ff)1.2 模式二&#xff1a;non-Fast-forward(–no-ff)1.3 模式三&#xff1a;fast-forward only(–ff-only) 2 git rebase3 区别 1 git merge git merge有好几种不同的模式 默认情况下你直接使用 git merge 命令&#x…

怎么还有人不清楚H3C认证考试流程?给你整理得明明白白

最近不少人都在后台问H3CIE资格认证考试&#xff08;全称&#xff1a;H3C认证路由交换互联网络专家&#xff09;&#xff0c;今天咱们就来聊一聊这个考试流程。第一次报考H3CIE-RS的考生需先参加新华三人才研学中心认可的H3CIE-RS认证培训。 H3C授权培训中心可在新华三官网查询…

迅为RK3568驱动教程第十八期-PWM

系统性PWM课程&#xff0c;完全掌握PWM。采用框架学习法&#xff0c;从基础知识、PWM子系统框架、API函数理论由面到点&#xff0c;逐个击破。通过SG90舵机&#xff0c;呼吸灯的控制把理论转为动手能力。最后从零实现输入捕获驱动程序&#xff0c;深入探究&#xff0c;体验一把…

Python爬虫小白入门(四)PhatomJS+Selenium篇下

一、前言 前文介绍了PhatomJS 和Selenium 的用法&#xff0c;工具准备完毕&#xff0c;我们来看看如何使用它们来改造我们之前写的小爬虫。 我们的目的是模拟页面下拉到底部&#xff0c;然后页面会刷出新的内容&#xff0c;每次会加载10张新图片。 大体思路是&#xff0c;用S…

list的特性及使用

1、list的介绍 1.list是序列容器&#xff0c;允许在序列的任何位置进行时间复杂度为o(1)的插入和删除操作&#xff0c;并且由双向迭代器。 2.list的底层是双链表&#xff0c;双链表不是物理上连续的储存空间&#xff0c;而是不同的地址空间通过next和prev指针连接成顺序表。 …

【每天学会一个渗透测试工具】AppScan安装及使用指南

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 https://www.hcl-software.com/appscan AppScan是一种综合型漏洞扫描工具&#xff0c;采用SaaS解决方案&#xff0c;它将所以…

【Tello无人机】使用Matlab完成控制器的设计—建模

模型辨识篇 在实际的无人机系统中&#xff0c;控制器的设计至关重要&#xff0c;它直接影响无人机的稳定性和响应能力。然而&#xff0c;要设计出高效、可靠的控制器&#xff0c;首先必须准确理解无人机的动态行为&#xff0c;这就需要通过收集输入输出数据来辨识其运动学模型。…

天池人脸识别项目复现

1 项目背景 #c 概述 项目的目的 图像分类是整个计算机视觉领域中最基础的任务&#xff0c;也是最重要的任务之⼀&#xff0c;最适合拿来进⾏学习实践。为了让新⼿们能够⼀次性体验⼀个⼯业级别的图像分类任务的完整流程&#xff0c;本次我们选择带领⼤家完成⼀个对图片中⼈脸进…

从0开始C++(二):类、对象、封装

目录 类&对象的概念 类的内容 对象的创建 ● 栈内存对象 ● 堆内存对象 封装 类&对象的概念 类和对象是一个比较抽象的概念&#xff0c;这里直接用一个实例方便理解。 类&#xff1a;类是一个抽象的概念&#xff0c;用来描述同一类对象的特点&#xff08;比如&am…