数据结构从入门到精通——堆排序

news2024/9/20 1:00:35

堆排序

  • 前言
  • 一、堆排序的基本思想
  • 二、堆排序的特性总结
  • 三、堆排序的动图展示
  • 四、堆排序的代码实现
    • 向上建堆
    • test.c


前言

堆排序是一种利用堆数据结构实现的排序算法。首先,它将待排序的数组构建成一个大顶堆或小顶堆。然后,通过不断将堆顶元素(最大或最小)与末尾元素交换并重新调整堆,使得数组逐渐有序。最后,当堆的大小减至1时,排序完成。堆排序的时间复杂度为O(nlogn),空间复杂度为O(1),具有稳定性和适用性广的优点。


一、堆排序的基本思想

在这里插入图片描述
在这里插入图片描述

堆排序的基本思想是将待排序的序列构造成一个大顶堆或小顶堆,此时,整个序列的最大值(或最小值)就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值或最小值),然后将剩余的堆重新构造成一个堆,这样就会得到新的最大值(或最小值)。如此反复执行,便能得到一个有序序列了。

具体实现时,首先需要根据给定的待排序数组构建一个初始堆。构建堆的过程通常是从最后一个非叶子节点开始,向上遍历每个节点,对每个节点进行下沉操作,以确保每个节点都满足堆的性质。下沉操作是堆排序中的关键步骤,它通过比较节点与其子节点的值,确保父节点的值大于(对于大顶堆)或小于(对于小顶堆)其子节点的值。

在堆构建完成后,堆的根节点就是序列中的最大(或最小)元素。将其与堆数组的最后一个元素交换,然后移除最后一个元素(或将其视为已排序部分),此时堆的大小减一。接着,对剩余部分重新进行下沉操作,以恢复堆的性质。这个过程重复进行,直到堆的大小减至1,此时序列已经完全排序。

堆排序的时间复杂度为O(nlogn),其中n是待排序序列的长度。这是因为构建初始堆的时间复杂度为O(n),而每次移除堆顶元素并重新构建堆的时间复杂度为O(logn)。由于这个过程需要重复n次,所以总的时间复杂度为O(nlogn)。堆排序是一种原地排序算法,因为它只涉及到元素之间的交换和移动,不需要额外的存储空间。

值得注意的是,堆排序是一种不稳定的排序算法。这是因为在构建堆和下沉的过程中,相同值的元素可能会改变它们的相对顺序。因此,如果稳定性是一个重要的考量因素,那么可能需要选择其他的排序算法,如归并排序或冒泡排序。

二、堆排序的特性总结

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

堆排序是一种基于二叉堆数据结构所设计的排序算法,它兼具选择排序和插入排序的优点,并在许多情况下展现出其独特的性能特点。

  1. 空间效率:堆排序是一种原地排序算法,这意味着它不需要额外的存储空间来辅助排序过程,除了原数组本身。这使得堆排序在处理大数据集时,相较于其他需要额外空间的排序算法,具有更高的空间效率。
  2. 时间效率:堆排序的时间复杂度在最坏情况下为O(nlogn),其中n是待排序元素的数量。这意味着无论输入数据的初始状态如何,堆排序都能保持相对稳定的性能。这一点在处理大型数据集时尤为重要,因为某些排序算法(如快速排序)在特定输入情况下可能会退化为O(n²)的时间复杂度。
  3. 不稳定性:堆排序是一种不稳定的排序算法。这意味着如果两个元素具有相同的值,它们在排序后的相对位置可能会改变。这在某些应用中可能是一个缺点,但在其他不需要保持元素相对位置不变的场景中则不是问题。
  4. 易于实现:堆排序的算法逻辑相对简单,容易理解和实现。尽管其背后的二叉堆数据结构可能初看起来有些复杂,但一旦理解了其基本原理,实现堆排序就会变得相对直观。
  5. 适用性:堆排序特别适用于外部排序,即当数据量太大,无法一次性加载到内存中进行排序时。通过将数据分割成小块,并在每个小块上建立堆,然后逐步合并这些堆,可以实现大数据集的有效排序。

综上所述,堆排序是一种高效、稳定、易于实现且适用性广的排序算法。尽管它在某些方面可能不如其他排序算法(如快速排序或归并排序)出色,但在许多实际应用中,它仍然是一种非常有用的排序工具。

三、堆排序的动图展示

堆排序

堆排序是一种基于二叉堆的排序算法,它通过构建最大堆或最小堆,然后不断删除堆顶元素并调整堆结构来实现排序。动图展示通常能够直观地展示堆排序的整个过程,包括初始堆的构建、堆顶元素的删除和堆的调整等步骤。通过动图展示,可以清晰地看到堆排序算法的执行过程,从而更好地理解和掌握该算法的实现原理。

四、堆排序的代码实现

向上建堆

详细可看这篇——堆
在这里插入图片描述

ps:上面是建堆的时间复杂度不是堆排序的时间复杂度

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

test.c

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

void HeapSort(int* a, int n);
void PrintArray(int* a, int n);
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustDown(int* 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[parent], &a[child]);
			parent = child;
			child = child * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void TestHeapSort()
{
	int a[] = { 5, 13, 9, 16, 12, 4, 7, 1, 28, 25, 3, 9, 6, 2, 4, 7, 1, 8 };
	//int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
	PrintArray(a, sizeof(a) / sizeof(int));

	HeapSort(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));
}


void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
void TestOP()
{
	srand(time(0));
	const int N = 10000;
	int* a1 = (int*)malloc(sizeof(int) * N);

	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
	}


	int begin1 = clock();
	HeapSort(a1, N);
	int end1 = clock();
	printf("HeapSort:%d\n", end1 - begin1);

	free(a1);
}

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()
{
	TestHeapSort();
	TestOP();

	return 0;
}

这段代码实现了堆排序算法。

首先,AdjustDown函数用来调整以parent为根节点的子树,使之符合大顶堆的性质。在每一次调整中,函数会找到parent节点的左右孩子中较大的那个,并与parent节点进行比较。如果孩子节点较大,则交换它们的值,并将parent更新为较大孩子的位置,继续向下调整。如果孩子节点不大于parent节点,则停止调整。这个过程保证了每一次调整都能将较大的元素移动到较下面的位置。

HeapSort函数首先通过调用AdjustDown函数将待排序数组调整为一个大顶堆。然后,它通过不断交换堆顶元素与最后一个元素,并缩小堆的范围来对堆进行排序。在每一次交换后,需要调用AdjustDown函数将交换后的堆顶元素移动到合适的位置。

最终,经过多次交换与调整,待排序数组就会按照从小到大的顺序排列好。

在这里插入图片描述


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

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

相关文章

MC10T1S-10BASE-T1S车载以太网转换器

10BASE-T1S车载以太网转换器 为10BASE-T1S车载以太网转换器&#xff0c;支持Multidrop bus line和Point-to-Point。采用的DB9接口类型&#xff0c;支持PLCA。10BASE-T1S是IEEE 802.3cg标准制定的IEEE汽车以太网的最新标准之一&#xff0c;采用UTP这样的一对无屏蔽的双芯电缆进行…

Uibot (RPA设计软件)财务会计Web应用自动化(批量开票机器人)

Uibot (RPA设计软件&#xff09;Mage AI智能识别&#xff08;发票识别&#xff09;———机器人的小项目友友们可以参考小北的课前材料五博客~ (本博客中会有部分课程ppt截屏,如有侵权请及请及时与小北我取得联系~&#xff09; 紧接着小北的前两篇博客&#xff0c;友友们我们…

webpack5零基础入门-13生产模式

1.生产模式介绍 生产模式是开发完成代码后&#xff0c;我们需要得到代码将来部署上线。 这个模式下我们主要对代码进行优化&#xff0c;让其运行性能更好。 优化主要从两个角度出发: 优化代码运行性能优化代码打包速度 2.生产模式准备 我们分别准备两个配置文件来放不同的…

【C++】为什么vector的地址与首元素地址不同?

文章目录 一、问题发现&#xff1a;二、结果分析三、问题解析 一、问题发现&#xff1a; &vector和&vector[0]得到的两个地址居然不相同&#xff0c;对数组array取变量名地址和取首元素地址的结果是相同的。这是为啥呢&#xff1f; 使用下面代码进行验证&#xff1a;…

流畅的 Python 第二版(GPT 重译)(五)

第九章. 装饰器和闭包 有人对将这个功能命名为“装饰器”的选择提出了一些抱怨。主要的抱怨是该名称与其在 GoF 书中的用法不一致。 名称 decorator 可能更多地归因于其在编译器领域的用法—语法树被遍历并注释。 PEP 318—函数和方法的装饰器 函数装饰器让我们在源代码中“标记…

单片机-- 数电(3)

编码器与译码器 译码 &#xff1a;将二进制代码转化为其他进制的代码 编码 &#xff1a;就是将其他代码转换为二进制码 编码器的类型 1二进制编码器 用n位二进制数码对2的n次方个输入信号进行编码的电路 2二-十进制编码器 将0到9十个十进制数转化为二进制代码的电路 2…

SpringCloud中的@EnableDiscoceryClient和@EnableFeignClients注解的作用解析、RPC远程过程调用

目录 EnableDiscoveryClient 服务发现的核心概念 服务注册中心 EnableDiscoveryClient注解的作用 服务心跳健康检查 使用示例 EnableFeignClients Feign简介 EnableFeignClients注解的作用 RPC&#xff08;Remote Procedure Call&#xff09; 参考链接 Spring Cloud…

C++项目 -- 负载均衡OJ(一)compile_server

C项目 – 负载均衡OJ&#xff08;二&#xff09;compile_server 文章目录 C项目 -- 负载均衡OJ&#xff08;二&#xff09;compile_server一、compile_server设计1.总体服务流程 二、compiler.hpp三、runner.hpp四、compile_run.hpp五、compile_server.cc5.1.编译功能调试&…

Dockerfile Docker Compose(实战总结)

Dockerfile & Docker Compose&#xff08;实战总结&#xff09; Dockerfile Dockerfile 是用来构建Docker镜像文件&#xff0c;是由一条条构建镜像所需的指令构成的脚步。 步骤&#xff1a; 编写Dockerfile 文件docker build 构建镜像docker run 运行镜像docker push 发…

【黄金手指】windows操作系统环境下使用jar命令行解压和打包Springboot项目jar包

一、背景 项目中利用maven将Springboot项目打包成生产环境jar包。名为 prod_2024_1.jar。 需求是 修改配置文件中的某些参数值&#xff0c;并重新发布。 二、解压 jar -xvf .\prod_2024_1.jar释义&#xff1a; 这段命令是用于解压缩名为"prod_2024_1.jar"的Java归…

【QT入门】实现一个简单的图片查看软件

声明&#xff1a;该专栏为本人学习Qt知识点时候的笔记汇总&#xff0c;希望能给初学的朋友们一点帮助(加油&#xff01;) 往期回顾&#xff1a; 【QT入门】qmake和cmake的简单区别-CSDN博客 【QT入门】VS qt和QtCreator项目的相互转换-CSDN博客 【QT入门】Qt架构与三个窗口的区…

航顺车规级SoC全新亮相,助推汽车智能化发展

受益于汽车电动化、智能化和网联化的推进&#xff0c;汽车车身域和座舱域MCU市场规模持续扩大。据统计&#xff0c;2021年中国车载芯片MCU市场规模达30.01亿美元&#xff0c;同比增长13.59%&#xff0c;预计2025年市场规模将达42.74亿美元。 在技术要求方面&#xff0c;对…

Docker如何端口映射?

Docker是一种流行的开源容器化平台&#xff0c;它允许开发者将应用程序和其依赖资源打包到一个称为容器的可移植单元中。Docker提供了强大的管理和部署工具&#xff0c;使得应用程序可以在不同的环境中运行&#xff0c;无需担心环境配置的问题。在使用Docker部署应用程序时&…

Websocket + Vue使用

这里有一篇文档可以参考一下> 闪现 POM文件 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.7.0</version> </dependency> WebSocketConf…

模型部署 - onnx的导出和分析 - onnx 的架构和 onnx helper 的使用 - 学习记录

onnx 的架构和 onnx helper 的使用 简介一、onnx 的架构二、onnx 实践2.1、 create - linear.onnx2.1.1、要点一&#xff1a;创建节点2.1.2、要点二&#xff1a;创建张量2.1.3、要点三&#xff1a;创建图 2.2、 create - onnx.convnet2.3、使用 onnx helper 导出的基本流程总结…

Web框架开发-Django模型层(数据库操作)

一、ORM介绍 MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动ORM是“对象-关系-映…

g++在windows下使用C++进程库无法传入参数<求助>

如题&#xff1a; windows11使用g的时候&#xff0c;想使用下线程库。但是就发现了如题的问题。在使用时&#xff0c;不传入参数时不会报错的&#xff0c;但是传入参数之后就产生了报错。 点击进入定义发现头文件定义明明是正确的。 具体报错如下图。

流畅的 Python 第二版(GPT 重译)(二)

第三章&#xff1a;字典和集合 Python 基本上是用大量语法糖包装的字典。 Lalo Martins&#xff0c;早期数字游牧民和 Pythonista 我们在所有的 Python 程序中都使用字典。即使不是直接在我们的代码中&#xff0c;也是间接的&#xff0c;因为dict类型是 Python 实现的基本部分。…

绝地求生:PUBG延长GPU崩溃时间新方法

相信大家都在被GPU游戏崩溃苦恼已久&#xff0c;PUBG这个游戏崩溃&#xff0c;跟超频是没有多大关系的&#xff0c;只要超频TM5过测&#xff0c;YC过测&#xff0c;或者双烤过测&#xff0c;就没问题。主要是这个游戏的优化不行&#xff0c;特别40系显卡&#xff0c;对内存条也…

申请双软认证需要哪些材料?软件功能测试报告怎么获取?

“双软认证”是指软件产品评估和软件企业评估&#xff0c;其中需要软件测试报告。 企业申请双软认证除了获得软件企业和软件产品的认证资质&#xff0c;同时也是对企业知识产权的一种保护方式&#xff0c;更可以让企业享受国家提供给软件行业的税收优惠政策。 那么&#xff0c;…