【数据结构】排序效率最优解之一:二叉树-堆

news2024/11/26 15:34:33

Hello everybody!今天打算给大家介绍一个功能比较强大的数据结构的基础,它不仅具有很高的应用价值而且排序效率很高。冒泡排序都知道叭,它的时间复杂度为O(n^2),而堆排序的时间复杂度为O(n*logn)。堆排序直接碾压冒泡排序。在c语言阶段,我曾给过大家qsort函数模拟实现的代码,我是以冒泡排序为底层逻辑实现的:时间复杂度为O(n^2)。而真正库文件中的qsort是以快排为底层逻辑实现的:时间复杂度为O(n*logn)。所以当我们排较长的数值时,肉眼可见的会发现自己模拟实现的qsort的效率远远不及库文件中的qsort。这就很好的体现了时间复杂度为O(n*logn)的数据结构的魅力所在!那好,废话不多说,我们直接开始叭!

1.二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

1.顺序结构

顺序结构存储就是使用数组来存储,一般使用数组只适合完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

2.链式存储

二叉树的链式存储结构是指,用链表来表示一颗二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。链式结构又分为二叉链和三叉链。在当前的知识将借助一般都是二叉链,后面的高阶数据结构如红黑树等会用到三叉链。

2.堆的基本结构

注意堆有大堆和小堆之分。

小堆:每个结点上的数据都比子结点上的数据小,故根结点为最小值。

大堆:每个结点上的数据都比子结点上的数据小,故根结点为最大值。

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

typedef int HPDataType;
typedef struct Heap {
	HPDataType* a;
	int size;
	int capacity;
}HP;

这是堆的结构。其中a为数组名,堆上的数全部存放在这个数组中。虽然用数组存放堆,但它们的逻辑结构为堆。

因为:每个父结点的下标*2+1得到其左边的子结点,父结点的下标*2+2得其右边得子结点。

当然也可以反过来推:不管是左边的子结点还是右边的子节点,它们的下标-1与2取整即可得到其父结点。因为小数部分会被省略!

void HeapInit(HP* php);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);//插入数据并向上调整调整
void HeapPop(HP* php);//删除堆顶(根节点)

int HeapSize(HP* php);
HPDataType HeapTop(HP* php);
bool HeapEmpty(HP* php);

这些是堆需要实现的一些接口。其中最核心的是HeapPush和HeapPop,因为涉及到数据的调整。

其他接口就比较简单,我也就简单介绍一下。

3.接口的实现

那我就按照小堆给大家实现叭!

3.1初始化&销毁

void HeapInit(HP* php) {
	assert(php);//检查php是否为空指针
	php->a = NULL;
	php->size = php->capacity = 0;
}

void HeapDestroy(HP* php) {
	assert(php);
	free(php->a);//将a指向的动态空间还给操作系统
	php->a = NULL;//置空,避免出现野指针
	php->size = php->capacity = 0;
}

这两个接口比较简单,给出的注释比较详细。我就不过多赘述了!

3.2插入数据

void Swap(HPDataType* p1, HPDataType* p2) {
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustUp(HPDataType* a, HPDataType child) {
	int parent = (child - 1) / 2;//找父结点
	while (child > 0) {
		if (a[child] < a[parent]) {//当子结点小于父节点时,交换
			Swap(&a[child], &a[parent]);
			child = (child - 1) / 2;//找上一个子节点
			parent = (parent - 1) / 2;//找上一个父节点
		}
		else {
			break;
		}
	}
}

void HeapPush(HP* php, HPDataType x) {
	assert(php);
	if (php->size == php->capacity) {
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;//当容量等于有效数据时,按二倍扩容
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);//动态开辟空间
		assert(tmp);//检查是否开辟成功
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);//向上调整
}

我大概说一下这个接口的思路:1.首先看到HeapPush,先检查动态数组的容量是否够用,不够用的话就扩容。检查容量过后进入AdjustUp

2.在AdjustUp中,可以通过子节点找父节点,如果不满足小堆的规则,那么父节点的数值和子节点的数值交换,然后下标继续向上调整。直到子节点的下标为零(到达根结点时)结束。

通过调试会发现,确实是一个堆,这个接口的功能正常。

3.3删除堆顶

void AdjustDown(HPDataType* a, int size, int parent) {
	int child = parent * 2 + 1;//通过根结点找子结点
	while (child<size) {
		if (child + 1 < size && a[child + 1] < a[child]) {//如果左孩子大于右孩子,用右孩子
			child++;
		}
		if (a[parent] > a[child]) {
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;//继续向下调整
		}
		else {
			break;
		}
	}
}

void HeapPop(HP* php) {
	assert(php);//检查指针
	assert(php->size > 0);//检查有效数据
	Swap(&php->a[0], &php->a[php->size - 1]);//头尾数值交换
	php->size--;//删除最后一个
	AdjustDown(php->a, php->size, 0);//根结点向下调整
}

3.4结点个数&访问根节点&判空

HPDataType HeapTop(HP* php) {
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

int HeapSize(HP* php) {
	assert(php);
	return php->size;
}

bool HeapEmpty(HP* php) {
	assert(php);
	return php->size == 0;
}

这三个接口就很简单啦!宝子们看懂应该没什么问题!

通过测试可以看到堆排序可以实现升序排列,并且效率更高!

4.代码

头文件:

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

typedef int HPDataType;
typedef struct Heap {
	HPDataType* a;
	int size;
	int capacity;
}HP;

void HeapInit(HP* php);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);//插入数据并向上调整调整
void HeapPop(HP* php);//删除堆顶(根节点)

int HeapSize(HP* php);
HPDataType HeapTop(HP* php);
bool HeapEmpty(HP* php);

源文件:

#include "Heap.h"
void HeapInit(HP* php) {
	assert(php);//检查php是否为空指针
	php->a = NULL;
	php->size = php->capacity = 0;
}

void HeapDestroy(HP* php) {
	assert(php);
	free(php->a);//将a指向的动态空间还给操作系统
	php->a = NULL;//置空,避免出现野指针
	php->size = php->capacity = 0;
}
void Swap(HPDataType* p1, HPDataType* p2) {
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustUp(HPDataType* a, HPDataType child) {
	int parent = (child - 1) / 2;//找父结点
	while (child > 0) {
		if (a[child] < a[parent]) {//当子结点小于父节点时,交换
			Swap(&a[child], &a[parent]);
			child = (child - 1) / 2;
			parent = (parent - 1) / 2;
		}
		else {
			break;
		}
	}
}

void HeapPush(HP* php, HPDataType x) {
	assert(php);
	if (php->size == php->capacity) {
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;//当容量等于有效数据时,按二倍扩容
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);//动态开辟空间
		assert(tmp);//检查是否开辟成功
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);//向上调整
}

void AdjustDown(HPDataType* a, int size, int parent) {
	int child = parent * 2 + 1;
	while (child<size) {
		if (child + 1 < size && a[child + 1] < a[child]) {
			child++;
		}
		if (a[parent] > a[child]) {
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else {
			break;
		}
	}
}

void HeapPop(HP* php) {
	assert(php);
	assert(php->size > 0);
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	AdjustDown(php->a, php->size, 0);
}

HPDataType HeapTop(HP* php) {
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

int HeapSize(HP* php) {
	assert(php);
	return php->size;
}

bool HeapEmpty(HP* php) {
	assert(php);
	return php->size == 0;
}

测试文件:

#include "Heap.h"
int main() {
	int a[] = { 4,6,2,1,5,8,2,9 };
	HP hp;
	HeapInit(&hp);
	for (int i = 0; i < 8; i++) {
		HeapPush(&hp, a[i]);
	}

	while(!HeapEmpty(&hp)){
		printf("%d", HeapTop(&hp));
		HeapPop(&hp);
	}
	return 0;
}

5.结语

那今天的讨论就到这里喽!不知道大家是否有所收获呢?可以把自己的疑问发在评论区!

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

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

相关文章

MySQL- CRUD

一、INSERT 添加 公式 INSERT INTO table_name [(column [, column...])] VALUES (value [, value...]); 示例&#xff1a; CREATE TABLE goods (id INT ,good_name VARCHAR(10),price DOUBLE ); #添加数据 INSERT INTO goods (id,good_name,price ) VALUES (20,华为手机,…

【SpringCloud系列】@FeignClient微服务轻舞者

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【Rust】所有权的认识

所有权 所有程序都必须管理其运行时使用计算机内存的方式。一些语言中具有垃圾回收机制&#xff0c;在程序运行时有规律地寻找不再使用的内存&#xff1b;在另一些语言中&#xff0c;程序员必须亲自分配和释放内存。 Rust 则选择了第三种方式&#xff1a;通过所有权系统管理内…

L型骨牌覆盖问题。

问题&#xff1a;解决一个2k*2k的特殊棋牌上的L型骨牌覆盖问题。 思路&#xff1a; 棋盘覆盖实现的基本方法为分治法 当k0时(1ⅹ1棋盘)&#xff0c;及特殊方格&#xff0c;骨牌数为0 当k >0时&#xff0c;将2kⅹ2k棋盘分割为4个2k-1ⅹ2k-1子棋盘了 特殊方格位于4个较小…

深入了解Java8新特性-日期时间API:OffsetDateTime类

阅读建议 嗨&#xff0c;伙计&#xff01;刷到这篇文章咱们就是有缘人&#xff0c;在阅读这篇文章前我有一些建议&#xff1a; 本篇文章大概24000多字&#xff0c;预计阅读时间长需要20分钟。本篇文章的实战性、理论性较强&#xff0c;是一篇质量分数较高的技术干货文章&…

python实现C++简易自动压行

突发奇想&#xff0c;想要将自己的c压行之后交上去。但是苦于手动压行效率太低&#xff0c;在网上搜索压行网站没有找到&#xff0c;突然发现压行不就是检查检查去个换行符吗。于是心血来潮&#xff0c;用python实现了一个简易压行程序。 首先&#xff0c;宏定义等带#的文件不…

计算机杂谈系列精讲100篇-【计算机应用】PyTorch部署及分布式训练

目录 C平台PyTorch模型部署流程 1.模型转换 1. 不支持的操作 2. 指定数据类型 2.保存序列化模型 3.C load训练好的模型 4. 执行Script Module PyTorch分布式训练 分布式并行训练概述 Pytorch分布式数据并行 手把手渐进式实战 A. 单机单卡 B. 单机多卡DP C. 多机多卡DDP D. L…

android13(T) 客制化预置语言列表

效果图 需求分析 这个列表界面一般都是后来手动添加后才现实的&#xff0c;通过分析源码发现通过如下值可控 adb shell settings get system system_locales zh-CN,ja-JP,en-AT 所以只需查询出这个值&#xff0c;然后加在 SettingProvider 中即可 隐藏 bug 如果客户要求默…

【SpringCloud】注册中心和Ribbon负载均衡

SpringCloud 1.Eureka注册中心 1.1 Eureka的作用 注册中心拉取服务负载均衡远程调用 order-service得知user-service实例地址流程&#xff1a; user-service服务实例启动后&#xff0c;将自己的信息注册到eureka-server&#xff08;Eureka服务端&#xff09;&#xff0c;称…

使用STM32 HAL库驱动光电传感器的设计和优化

光电传感器在许多应用中起着重要的作用&#xff0c;例如自动计数、距离测量等。STM32微控制器和HAL库提供了丰富的功能和易于使用的接口&#xff0c;使得光电传感器的设计和优化变得更加便捷。本文将介绍如何使用STM32 HAL库驱动光电传感器的设计和优化&#xff0c;包括硬件设计…

电子印章管理系统:是什么、3个平台推荐

说到印章&#xff0c;相信看过近现代电视剧的人都见过&#xff0c;一般在订立合约时最常用到&#xff0c;双方在合约上加盖印鉴&#xff0c;即代表着合约的成立。 我小时候还见过我父亲的印章&#xff0c;只是随着时代的发展&#xff0c;印章因为不易携带&#xff0c;容易被盗…

livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号

livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号 livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号livox 介绍更换环境更换livox激光雷达型号 livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号 livox 介绍 览沃科技有限公司&#xff08;Livox&#xff…

java设计模式学习之【原型模式】

文章目录 引言原型模式简介定义与用途实现方式UML 使用场景优势与劣势原型模式在spring中的应用员工记录示例代码地址 引言 原型模式是一种创建型设计模式&#xff0c;它允许对象能够复制自身&#xff0c;以此来创建一个新的对象。这种模式在需要重复地创建相似对象时非常有用…

开放式耳机性价比排行榜、开放式耳机性价比排行榜前十

目前市面上的蓝牙耳机品类繁多&#xff0c;开放式蓝牙耳机是这几年比较新兴的&#xff0c;因为不入耳、佩戴舒适、安全系数高等优点&#xff0c;一直备受运动人士追捧&#xff0c;还有像我们这样日常用但是耳道比较小或者戴入耳式觉得头晕恶心的人&#xff0c;开放式耳机真的是…

【开发实践】网页预览excel表格原版样式

一、需求分析 由于业务部门需要&#xff0c;在导出excel表格页面&#xff0c;不需要先下载&#xff0c;就可以直接在页面上预览该表格文件。 二、代码实现 使用Luckysheet实现&#xff1a; 什么是Luckysheet Luckysheet &#xff0c;一款纯前端类似excel的在线表格&#xff0…

求和(打表题)

题目 打个表发现当 n 时答案为 p &#xff0c;否则为 1 &#xff0c;然后套板子。 #include <iostream> #include <algorithm> #include <vector> #include <cstring> #include <cmath>using namespace std;#define int long long using i64 …

西南科技大学数字电子技术实验二(SSI逻辑器件设计组合逻辑电路及FPGA实现 )预习报告

一、计算/设计过程 说明:本实验是验证性实验,计算预测验证结果。是设计性实验一定要从系统指标计算出元件参数过程,越详细越好。用公式输入法完成相关公式内容,不得贴手写图片。(注意:从抽象公式直接得出结果,不得分,页数可根据内容调整) 1、1位半加器 真值表: 逻…

虚幻学习笔记6—摄像机控制

一、前言 摄像机在虚幻中的应用是最常见的。如通常在游戏或应用中会常常出现需要切换不同视角的情况、摄像机拉近缩小等&#xff0c;这个在虚幻中是怎么实现的呢。 二、实现视点切换 2.1、提前设置场景的视点&#xff1a;如图2.1.1所示添加一个摄像机视点到关卡场景中&#x…

2.ORB-SLAM3中如何从二进制文件中加载多地图、关键帧、地图点等数据结构

目录 1 为什么保存&加载(视觉)地图 1.1 加载多地图的主函数 1.2 加载各个地图 Atlas::PostLoad 1.3 加载关键帧及地图点Map::PostLoad 1.4 恢复地图点信息 MapPoint::PostLoad 1.5 恢复关键帧信息KeyFrame::PostLoad 1 为什么保存&加载(视觉)地图 因为我们要去做导…

kali linux nmap 端口扫描 简单教程

本次实验所用工具如下&#xff1a; VMwarekali linux (namp扫描工具)Windows sever 2016 需开启&#xff08;FTP&#xff0c;smp&#xff0c;Telnet&#xff0c;rdp&#xff09;端口namp操作所用部分代码&#xff1a; -sP ping 扫描 -P 指定端口范围 -sV 服务版本探测 -A …