堆排序(C语言)

前言

        在上一篇内容:大小堆的实现(C语言),我们实现了关于创建大小堆的各函数与实现。但是如果突然要使用一个堆排序但是此时并没有一个现成的堆,这就需要花费时间去新建实现堆的插入删除这些操作从而实现一个堆,并且在插入的过程中存在内存空间的消耗(malloc空间),那是否有一些其它办法可以避免以上问题呢?

数组建堆

我们能不能不消耗空间就完成一次建堆?直接将所给的数组建堆可以吗?

这些数字在物理逻辑上是一个数组,而在虚拟逻辑上是一个完全二叉树,那么将这个完全二叉树进行一些调整后是不是就能得到一个堆呢?我们尝试利用之前的向上调整算法,完成建堆的操作:

上述操作的本质就是模拟堆插入的过程建堆

(第一个视为堆,第二个插入然后向上调堆、第一个和第二个视为堆,第三个插入然后向上调堆)

可以发现我们在没有申请内存空间的前提下,仅利用一个向上调整算法就完成了将数组建堆的操作,并最终到了一个小堆,但是如果我们在选出这个小堆中的最小值后,再想要选出这个堆中的次小值就会出现问题:

可以发现当要选出次小值时,缺少了”1“的小堆的剩余元素并不能算作是一个小堆它们都是无序的,所以为了选出次小值,我们还要建堆(将数组元素向上调整建堆)以此类推这就相当于选一次值就要建一次堆,那还不如直接选用时间复杂度为O(n)的暴力遍历数组选取最小值,次小值,而我们建堆的目的就是为了方便我们选出我们想要的最大/小、次大/小的数,但是很明显如果将利用向上调整算法建立一个小堆是无法满足我们的需求的,所以我们应该利用向上调整算法去建立一个大堆。

结论:若利用向上调整算法建小堆,如果取出最小值,那么剩下的数有可能不是堆,故应建大堆 

只需要该算法做一些调整,将(a[child] < a[parent])变为(a[child] > a[parent]),就可以建大堆:

调整后的结果是我们得到了一个大堆,此时即使将堆顶的“9”删除,剩余的两个子树也仍然是大堆的形式,这就意味着我们在选出最小数或次小数后只需要进行向下调整即可不需要考虑重新建堆,当我们想要最小值,只需要交换首尾元素的位置,取出此时的堆顶元素即可,想要获取次小值,只需要将尾部的数不再视为堆的一部分再次重复以上操作即可:

堆排序

其实上述的内容,就是我们实际中堆排序的一种实现方式,即利用向上调整算法建大堆然后再利用向下调整算法将挑选后剩余的元素重新调整为虚拟逻辑上的大堆以便下一次的选取

利用向上/下调整算法将数组中的数字在虚拟逻辑上重新变为大/小堆与重新建堆的区别是?

在堆排序算法中,我们需要构建一个最大/小堆来进行排序。这可以通过两种方式实现:

1. 重新建堆:这是一种直接的方法,从数组的首元素开始逐个插入到空堆中,步骤如下:

  • 创建一个空的堆
  • 将数组中的元素逐个插入到空堆中
  • 最终得到了一个满足最大(或最小)堆性质的完整二叉树

2. 向上/下调整:这是一种当已有部分构成的近似完全二叉树进行调整来达到目标状态的方法

  • 向上调整:从某节点开始,将其与父节点比较并交换位置,直至满足最大/小堆性质。该过程会将当前节点及其祖先节点推向正确位置,并保持子树仍然满足对应性质。
  • 向下调整:从某节点开始,将其与左右子节点比较并交换位置,直至满足最大/小堆性质。该过程会使当前节点及其后代子树变为有序树,并保持其他部分仍然满足对应性质

3、区别:

  1. 重新建堆是一种从零开始构建堆的方法,它将整个数组视为初始状态,并按顺序插入元素来构建最大(或最小)堆。这种方法需要较多的时间和空间复杂度。
  2. 向上调整和向下调整算法是在已有部分近似完全二叉树的基础上进行调整,通过比较节点与其父节点或子节点来达到维护最大(或最小)堆性质的目标。这两种算法可以在不重建完全二叉树的情况下对特定位置进行优化操作,因此具有更高效率。

4、总结:

  1. 如果已拥有一个无序数组并希望将其转换为一个满足最大/小堆性质的完全二叉树,则可以使用重新建堆方法
  2. 如果你已经拥有一个近似完全二叉树,并且只需对其中某些位置进行修正以满足最大(或最小)堆性质,则应使用向上调整或向下调整算法

利用向上调整算法实现堆排序

使用向上调整算法实现堆排序的步骤如下:

  1. 构建最大堆:从数组的第一个非叶子节点开始,依次对每个节点进行向上调整操作,使其满足最大堆性质。可以通过遍历非叶子节点并依次调用向上调整函数来完成这一步骤。

  2. 排序:将根节点(数组首元素)与末尾元素交换位置,并将末尾元素从堆中移除。然后对新的根节点进行一次向下调整操作,以维持最大堆性质。重复这个过程直到所有元素都被移除并排好序。

具体实现代码如下所示(假设数组是从索引 0 开始):

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>

//向上调整,此时传递过来的是最后一个孩子的元素下标我们用child表示
void AdjustUP(HPDataType* a,int child)
{
	//由于我们要调整父亲与孩子的位置所以此时也需要父亲元素的下标,而0父亲元素的下标值 = (任意一个孩子的下标值-1)/ 2 
	int parent = (child - 1) / 2;
	//当孩子等于0的时位于树顶(数组首元素的位置),树顶元素没有父亲,循环结束
	while(child > 0)
	{
		//如果孩子还未到顶且它的下标对应的元素值小于它的父亲的下标对应的元素值,就将父子位置交换,交换玩后还要将下标对应的值“向上移动”
		//if (a[child] < a[parent])
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		//由于这是一个小堆,所以当孩子大于等于父亲时不需要交换,直接退出循环即可
		else
		{
			break;
		}
	
	}
}

//向下调整算法
void AdjustDown(HPDataType* a, int size, int parent)
{
	//根据之前的推论,左孩子的下标值为父亲下标值的两倍+1,左孩子的下标值为父亲下标值的两倍+2
	int child = parent * 2 + 1;
	//循环结束的条件是走到叶子结点
	while (child < size)
	{
		//假设左孩子小,若假设失败则更新child,转换为右孩子小,同时保证child的下标不会越界
		//if (child + 1 < size && a[child + 1] < a[child])
		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 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[] = { 4,6,2,1,5,8,2,9 };
	HeapSort(a, sizeof(a) / sizeof(int));
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	//HeapSort(a, i);
	return 0;
}

利用向下调整算法实现堆排序

1、从最后一个结点的父亲开始向下调整

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>

//向下调整算法
void AdjustDown(HPDataType* a, int size, int parent)
{
	//根据之前的推论,左孩子的下标值为父亲下标值的两倍+1,左孩子的下标值为父亲下标值的两倍+2
	int child = parent * 2 + 1;
	//循环结束的条件是走到叶子结点
	while (child < size)
	{
		//假设左孩子小,若假设失败则更新child,转换为右孩子小,同时保证child的下标不会越界
		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 HeapSort(int* a, int n)
{
    //构建最小堆
	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;
	}

}

int main()
{
	int a[] = { 4,6,2,1,5,8,2,9 };
	HeapSort(a, sizeof(a) / sizeof(int));
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	//HeapSort(a, i);
	return 0;
}

~over~

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

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

相关文章

Oracle-CDB容器数据库修改service_names踩坑

前言: 最近在对一套Oracle容器数据库进行迁移测试时&#xff0c;为了保持新环境与旧环境的服务名一致&#xff0c;需要在新环境添加旧环境的服务名&#xff0c;在CDB的根容器通过service_name参数添加旧环境的服务名之后&#xff0c;发现数据库PDB的服务名全部被注销&#xff0…

今日思考 -- 创新领导力(CIO)读后感

收获3个观点&#xff1a; 1 &#xff0c;IT DT 商业&#xff0c;才是未来IT人的出路之一 &#xff01; 2 &#xff0c;在CXO中&#xff0c;CIO像CEO一样&#xff0c;具备了整个企业的业务全视角 &#xff0c;同时也更具解决 ‘’系统性‘’问题的能力 &#xff01; 3 &…

go并发编程(中)

目录 一、并发安全性 1.1 变量并发安全性 1.2 容器并发安全性 二、多路复用 三、协程常见的面试题 3.1交替打印奇数偶数 一、并发安全性 1.1 变量并发安全性 这个和C中并发安全是一样的&#xff0c;主要是多个线程对临界资源的同时访问&#xff0c;最经典的就是 n操作…

网络层之IP数据报格式、数据报分片、IPv4、子网划分和子网掩码

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

openmmlab环境搭建及模拟kitti数据集跑pointpillars模型

点云训练—openmmlab环境搭建及模拟kitti数据集跑pointpillars模型 1 环境搭建 在我的 linux 服务器上&#xff0c;基于ubuntu20.04 参见&#xff1a;开始你的第一步 — MMDetection3D 1.3.0 文档 1.1 本地环境已安装anaconda. anaconda的安装参见博文&#xff1a;DS6.1-Y…

Linux 基本语句_14_信号灯实验

原理&#xff1a; Send进程通过建立共享内存区域&#xff0c;并向其中写入数据&#xff0c;Recive通过与共享内存连接读取其中的数据。 但是如果进程进行读取操作的时候其他进程再次写入会产生数据丢失&#xff0c;产生竞态&#xff0c;为了确保在某段时间内只有一个操作&…

Leetcode—1038.从二叉搜索树到更大和树【中等】

2023每日刷题&#xff08;四十九&#xff09; Leetcode—1038.从二叉搜索树到更大和树 算法思想 二叉搜索树的中序遍历&#xff08;左根右&#xff09;结果是一个单调递增的有序序列&#xff0c;我们反序进行中序遍历&#xff08;右根左&#xff09;&#xff0c;即可以得到一…

基于Java SSM框架实现母婴儿用品网站系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现母婴儿用品网站系统演示 摘要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 母婴用品网站&#xff0c;主要的模块包括管理员&#xff1b;主页、个人中心、用户管理、商品分…

wireshark自定义协议插件开发

目录 脚本代码 报文显示 脚本代码 local NAME "test" test_proto Proto("test", "test Protocol") task_id ProtoField.uint16("test.task_id", "test id", base.DEC) cn ProtoField.uint8("test.cn", &qu…

数学建模-数据新动能驱动中国经济增长的统计研究-基于数字产业化和产业数字化的经济贡献测度

数据新动能驱动中国经济增长的统计研究-基于数字产业化和产业数字化的经济贡献测度 整体求解过程概述(摘要) 伴随着数据要素化进程的不断加深&#xff0c;对于数据如何作用于经济发展&#xff0c;数据与其他要素结合产生的动能应该如何测度的研究愈发重要。本文将数据新动能分…

最热门超声波清洗机有哪些?热门超声波清洗机推荐

眼镜党朋友第一次接触超声波清洗机应该是在眼镜店的时候&#xff0c;把眼镜拿给老板他几分钟就搞定眼镜清洗的&#xff0c;是的没有错&#xff0c;那个机器叫超声波清洗机&#xff0c;不需要自己动手就可把眼镜清洗干净的一款智能清洁工具&#xff0c;它的出现可以说是方便了我…

计算机网络扫盲(4)——时延

一、概述 在这里&#xff0c;我们考虑分组交换网的情况&#xff0c;因特网可以被看成是一种基础设施&#xff0c;该基础设施为运行在端系统上的分布式应用提供服务。在理想情况下&#xff0c;我们希望因特网服务能够在任意两个端系统之间随心所欲地移动数据而没有任何数据地丢失…

软信天成:数据泄露日趋严重 “资产”保护何去何从

随着数据应用的逐渐深入&#xff0c;越来越多的企业意识到&#xff1a;数据作为信息的载体&#xff0c;可以成为企业知识产权、收益流和具备竞争优势的基础资产。然而&#xff0c;当包含大量敏感信息的数据被视作资产时&#xff0c;亦将直面信息被“窃取”、“泄露”和“滥用”…

CrapApi部署手册( maven+tomcat+idea)

目录 一、本章节所用到的资源共享&#xff0c;嫌麻烦的可以直接下载本地配置好运行使用二、idea maven tomcat启动&#xff0c;我的maven和tomcat的配置三、遇到的问题四、项目运行后效果图转载请标明出处&#xff0c;写作不易如果有用请给个赞~~~~~~~~~~~~~~~~~~~~~~~~~~~~~…

跨网文件摆渡系统:安全、可控的数字传输桥梁

在企业高度信息化的时代&#xff0c;数据的流通与共享已经成为企业、组织乃至个人之间不可或缺的沟通方式。然而&#xff0c;在数据流通的过程中&#xff0c;我们经常会遇到各种难题和挑战&#xff0c;尤其是当涉及到不同网络环境之间的文件传输。这不仅需要保证文件的安全性&a…

基于Java SSM框架实现人才小区公寓社区物业管理系统项目【项目源码+论文说明】

基于java的SSM框架实现人才小区公寓社区物业管理系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个人才公寓管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff…

MySQL练习题,学生成绩查询练习题,附带答案

题目 (一) 新建以下几个表 student(学生表)&#xff1a; snosnamesexdeptbirthagePhone 其中约束如下&#xff1a; &#xff08;1&#xff09; 学号不能存在相同的 sno int auto_increment primary key &#xff08;2&#xff09; 名字为非空 sname varchar(20) not nu…

Excel如何设置在未打印时显示虚线打印时不显示虚线

记得之前分享过一个BOM表模板&#xff0c;但是在我打印时&#xff0c;发现明明是留空白的地方却打印出来的虚线 后来&#xff0c;看了自己的页面布局&#xff0c;原来是网格线设置错误了 当我设置为查看时显示网格线&#xff0c;打印时不显示网格线&#xff0c;这样就正常了

二百一十、Hive——Flume采集的JSON数据文件写入Hive的ODS层表后字段的数据残缺

一、目的 在用Flume把Kafka的数据采集写入Hive的ODS层表的HDFS文件路径后&#xff0c;发现HDFS文件中没问题&#xff0c;但是ODS层表中字段的数据却有问题&#xff0c;字段中的JSON数据不全 二、Hive处理JSON数据方式 &#xff08;一&#xff09;将Flume采集Kafka的JSON数据…

maven篇---第二篇

系列文章目录 文章目录 系列文章目录前言一、什么是Maven的坐标?二、讲一下maven的生命周期三、说说你熟悉哪些maven命令?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的…
最新文章