【数据结构初阶】--- 堆

文章目录

    • 一、什么是堆?
      • 二叉树
      • 完全二叉树
      • 堆的分类
      • 堆的实现方法
    • 二、堆的操作
      • 堆的定义
      • 初始化
      • 插入数据(包含向上调整详细讲解)
      • 向上调整
      • 删除堆顶元素(包含向下调整详细讲解)
      • 向下调整
      • 返回堆顶元素
      • 判断堆是否为空
      • 销毁
    • 三、建堆
      • 向上调整建堆
      • 向下调整建堆

一、什么是堆?

堆其实就是个完全二叉树

先了解一下什么是树
就像链表中的节点,不过树中的节点至少指向一个或多个节点,但是,一个结点不能被多个节点指向
在这里插入图片描述

二叉树

堆是完全二叉树,那完全二叉树什么样呢?
先看看二叉树
像这样的,与普通的树不同在于二叉树中的每个结点只能指向两个不同的结点
在这里插入图片描述

完全二叉树

完全二叉树:

  • 最后一行的节点从左到右是连续的,最少可以是一个结点
  • 其余行必须是满的
  • 当最后一行满结点时又叫做满二叉树,这是完全二叉树的特殊情况

接下来就要看看一下完全二叉树的样子
在这里插入图片描述

堆的分类

堆分为大堆和小堆

  • 小堆:树任何一个父亲都小于等于孩子
    在这里插入图片描述
  • 大堆:树任何一个父亲都大于等于孩子
    在这里插入图片描述

堆的实现方法

上面用图描述的堆实际上是逻辑模型,而真实的存放这些数据是用数组来存放,这也叫物理模型
90存在数组下标为0的位置,75存在数组下标为1的位置,80存在数组下标为2的位置,以此类推
好像明明可以用链表存储,为什么非要用数组呢?
因为根据堆的特点,是可以对无序的数组进行排序,并且时间复杂度是O(N*logN)级别的,这样的速度是很优秀的。

二、堆的操作

//堆的初始化
void HeapInit(Heap* hp);
//堆的销毁
void HeapDestory(Heap* hp);

//向堆中插入数据
void HeapPush(Heap* hp, HDataType x);
//删除堆顶元素
void HeapPop(Heap* hp);
//返回堆顶数据
HDataType HeapTop(Heap* hp);
//判断堆是否为空
bool HeapEmpty(Heap* hp);

//向上调整
void AdjustUp(int* arr, int n);
//向下调整
void AdjustDown(int* arr, int n, int pos);//n是数组的个数,pos是开始调整的位置

堆的定义

堆虽然是个完全二叉树,但它是为了服务数组实现高效排序,因此我们就用数组来实现堆

typedef int HDataType;
typedef struct Heap
{
	HDataType* arr;
	int size;//数组的有效存储个数
	int capacity;//数组的容量
}Heap;

初始化

给堆初始化时我并没有给其开辟空间,所以后面插入数据时才会开辟,那么capacity和size自然也是0,给指针arr置空

void HeapInit(Heap* hp)
{
	assert(hp);

	hp->arr = NULL;
	hp->capacity = 0;
	hp->size = 0;
}

插入数据(包含向上调整详细讲解)

首先就是判断空间够不够,size如果等于capacity,那就表示已经满了,需要先扩容,这里是扩容,所以要用realloc,如果用malloc,那么之前的数据就丢失了,因为malloc的作用是重新开辟一块新的空间,而不是扩容,扩容是需要使原来的数据保存下来,并且有了新的空间区存放新的数据
接下来就是到了重要的时刻,向上调整AdjustUp,我用小堆来讲解
因为我们要在插入数据的同时要保证数组中的数据存储顺序与逻辑模型中的堆保持一致,新的数据是插在数组末尾的,现在整体看来已经不满足小堆了
在这里插入图片描述

  • 每插入一个数据时,只与自己的父亲比较,如果自己比父亲小,那就与父亲交换值,交换后,再与当前位置的值进行比较,直至比父亲大的时候或者已经到根节点的时候,停止比较
  • 为什么只与自己的父亲比较呢,因为当你插入时,此时的数据已经是个小堆了,那么你的到来只会影响你跟你父亲、你父亲的父亲…这些位置,所以每次跟这条线路的值比较就行
  • 无论这个孩子是左孩子还是右孩子,想要找到父亲的位置,只需parent = (child-1)/2,就可以找到父亲的下标
void HeapPush(Heap* hp, HDataType x)
{
	assert(hp);

	if (hp->size == hp->capacity)
	{
		int new_capacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		HDataType* p = (HDataType*)realloc(hp->arr, sizeof(HDataType) * new_capacity);
		if (p == NULL)
		{
			perror("realloc fail");
			return;
		}
		hp->arr = p;
		hp->capacity = new_capacity;
	}
	hp->arr[hp->size] = x;
	hp->size++;

	AdjustUp(hp->arr, hp->size);//当前个数
}

向上调整

void AdjustUp(int* arr, int n)
{
	int child = n - 1;
	int parent = (child - 1) / 2;

	while (child > 0)
	{
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

删除堆顶元素(包含向下调整详细讲解)

  • 为什么是删除堆顶元素而不是删除堆的最后一个元素,毕竟插入的时候就是尾插
  • 因为堆顶的元素是整个数组中最小的元素,我们可以利用这一特性来寻找数组中前多少位小的数,就像奶茶店寻找评分最高的十家店,现在是小堆,那我们就利用小堆寻找评分最低的十家店。
  • 如果我们将现在的堆顶元素删除,那么现在没有了堆顶元素,谁来当堆顶元素呢,是像数组删除头元素,然后整体向前移一位吗?听起来好像有点意思,那就分析一下吧
    在这里插入图片描述
  • 左边是排好的小堆,右边是删除了20后,数组中每个数据向前移一位形成的“小堆”,此时,不难看出,这根本已经不是堆了,为什么?
  • 因为堆的性质,父亲节点要小于等于两个孩子,与兄弟节点的大小没有任何关系,现在你变成了第二张图,30,原本是25的兄弟,他俩的大小本身是没有关系的,但是经过刚才的操作,30变成了25的父亲,在小堆中,作为父亲,就要比孩子小,原本我们是兄弟没有大小关系,我可以比你大,可以比你小,现在变成了父子,就有了大小关系,自然就有可能就不符合小堆的条件,
  • 那么,还有没有挽回的余地呢,有的,将现在的数据重新排成小堆,但你要知道,删除一个堆顶元素,就要重新排一次全部数据,这样的代价是非常大的。有没有更好的方法,接下来我就会讲

向下调整

  • 想删除堆顶元素,那就与最后一个元素进行交换,然后size–,此时新的堆顶元素作为父亲与两个孩子中较小的一位比较大小,父亲比孩子大,那就交换,交换后的父亲继续与孩子比较,直至父亲比孩子小或者孩子的下标大于数组长度,到此停止,现在的数组存储数据的顺序,依旧是小堆。
  • 为什么是和两个孩子中较小的比较呢?假设一下吧,首先作为父亲的两个孩子,他俩的大小是没有直接关系的,谁都可以比对方大或小,假设一个孩子大一个孩子小,父亲要跟孩子比,分三种情况,
    1. 父亲最大,与大孩子比较,父亲大,所以与大孩子交换,交换后,大孩子在父亲的位置,那就要比两个孩子都小,但大孩子本身就比小孩子大,所以还要和小孩子交换,经历了两次交换。
    2. 父亲比大孩子小,比小孩子大,那么父亲需要和小孩子交换,交换后,小孩子比之前的父亲和大孩子都小,所以只交换一次。
    3. 父亲是最小的,因此不需要交换,但是父亲即使没有交换,也是比较了大小才确认的。

总结:
当父亲之和小孩子比较大小时,要么交换一次要么不交换;与大孩子比较时,交换两次,交换一次,和不交换;相比之下,如果只与小孩子比较就会节省交换两次的操作,因此父亲与小孩比较大小就行。

void HeapPop(Heap* hp)
{
	assert(hp);

	Swap(&hp->arr[0], &hp->arr[hp->size - 1]);
	hp->size--;
	AdjustDown(hp->arr, hp->size,0);//传的是当前的个数
}

向下调整

思路:

  • 先将末尾的值与堆顶元素交换,此时想要保持整个数组还是小堆,就让现在的交换后堆顶元素向下调整
  • 小堆的话,与两个孩子中较小的比较大小,如果比那个孩子小,那就交换,交换后重复这个过程,直至遇到较小的孩子都比自己大时或者自己的孩子的下标已经超出整个数组的大小,那就停止
void AdjustDown(int* arr, int n,int pos)
{
	int parent = pos;
	int child = parent * 2 + 1;

	while (child < n)
	{
		if (((child + 1) < n) && (arr[child] > arr[child + 1]))
		{
			child = child + 1;
		}
		if (arr[parent] > arr[child])
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
}

返回堆顶元素

HDataType HeapTop(Heap* hp)
{
	assert(hp);
	assert(!HeapEmpty(hp));

	return hp->arr[0];
}

判断堆是否为空

bool HeapEmpty(Heap* hp)
{
	assert(hp);

	return hp->size == 0;
}

销毁

void HeapDestory(Heap* hp)
{
	assert(hp);

	free(hp->arr);
	hp->arr = NULL;
	hp->capacity = 0;
	hp->size = 0;
}

三、建堆

意义:将乱序的数组调整成堆

向上调整建堆

虽然数组已经有了数据,但我们把建堆的过程看做插入数据,
在这里插入图片描述

向下调整建堆

  • 不论是向上调整还是向下调整,都有前提,除当前位置,剩余的位置已经是堆了,比如向上调整,这个数到来之前就已经是个堆了,只不过由于它的到来,更改了堆的结构,所以向上调整为新的堆
  • 那么向下调整也一样吗?首先想到的应该是从堆顶开始向下调整,关键是你是第一个元素,但其余的元素并不是堆,怎么办,要想向下调整,首先之前的结构要是堆。
  • 方法不太好想,我就来讲解一下,堆是完全二叉树,它的叶子结点实际也就是一个个堆,比如图中的65、10、70、15都是堆,那么我们倒着向下调整来组建堆,这四个数已经是堆了,所以从80开始
    在这里插入图片描述

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

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

相关文章

Docker安装(内网无网环境),亲测简单易懂

文章目录 前言一、安装环境二、安装步骤三、启动四、查看状态总结 前言 Docker安装&#xff08;内网无网环境&#xff09;&#xff0c;亲测简单易懂 一、安装环境 CentOS Linux release 7.x Docker版本&#xff1a;18.09.8 二、安装步骤 &#xff01;&#xff01;&#xf…

网络学习(三)TCP三次握手、四次挥手,及Wireshark抓包验证

目录 一、什么是 TCP 三次握手&#xff1f;二、什么是 TCP 四次挥手&#xff1f;三、Wireshark抓包验证3.1 如何捕获三次握手、四次挥手3.2 TCP 三次握手的记录3.3 数据传输3.4 TCP 四次挥手的记录 一、什么是 TCP 三次握手&#xff1f; TCP&#xff08;Transmission Control …

计算机组成原理之存储器(二)

文章目录 随机读写存储器RAM静态MOS存储单元与存储芯片动态MOS存储单元与存储芯片 半导体存储器逻辑设计存储器的读写以及刷新存储器的读写动态存储芯片的刷新 随机读写存储器RAM 静态MOS存储单元与存储芯片 静态RAM用半导体管的导通和截止来记忆&#xff0c;只要不掉电&#x…

2.线性神经网络

目录 1.线性回归一个简化模型线性模型&#xff1a;可以看做是单层神经网络衡量预估质量训练数据参数学习显示解总结 2.基础优化方法小批量随机梯度下降总结 3.Softmax回归&#xff1a;其实是一个分类问题回归VS分类从回归到多类分类---均方损失Softmax和交叉熵损失 4.损失函数L…

使用插件永久解决IDEA使用Shift+F10失效问题(不需要换老版本输入法)

在日常编程中&#xff0c;使用快捷键可以大大提高开发效率。然而&#xff0c;有时候我们会遇到IDEA 中&#xff0c;ShiftF10 快捷键失效。这个蛋疼的问题现在终于可以得到解决&#xff0c;上个月在逛V2EX的时候看见一位大佬做的插件。 大佬链接&#xff1a;https://www.v2ex.c…

编程精粹—— Microsoft 编写优质无错 C 程序秘诀 02:设计并使用断言

这是一本老书&#xff0c;作者 Steve Maguire 在微软工作期间写了这本书&#xff0c;英文版于 1993 年发布。2013 年推出了 20 周年纪念第二版。我们看到的标题是中译版名字&#xff0c;英文版的名字是《Writing Clean Code ─── Microsoft’s Techniques for Developing》&a…

51单片机宏定义的例子

代码 demo.c #include "hardware.h"void delay() {volatile unsigned int n;for(n 0; n < 50000; n); }int main(void) {IO_init();while(1){PINSET(LED);delay();PINCLR(LED);delay();}return 0; }cfg.h #ifndef _CFG_H_ #define _CFG_H_// #define F_CPU …

nacos注册中心配置中心集群搭建

文章目录 学习连接1.Nacos安装与简单使用1.1. Nacos安装指南Windows安装下载安装包解压端口配置启动访问 Linux安装安装JDK上传安装包解压端口配置启动 1.2.服务注册到nacos使用步骤引入依赖配置nacos地址重启 示例父工程pom.xmluser-servicepom.xmlapplication.ymlUserApplica…

Jupyter Notebook 中 %run 魔法命令

目录 基本用法运行 Python 脚本运行 Jupyter Notebook 的其他单元格传递命令行参数 示例运行 Python 脚本示例运行其他 Jupyter Notebook 示例传递命令行参数示例 注意事项与 import 命令的区别%runimport 结论 %run 是 Jupyter Notebook 中的一个强大工具&#xff0c;它允许你…

【机器学习】第4章 决策树算法(重点)

一、概念 1.原理看图&#xff0c;非常简单&#xff1a; &#xff08;1&#xff09;蓝的是节点&#xff0c;白的是分支&#xff08;条件&#xff0c;或者说是特征&#xff0c;属性&#xff0c;也可以直接写线上&#xff0c;看题目有没有要求&#xff09;&#xff0c; &#xff0…

MySQL----InooDB行级锁、间隙锁

行级锁 行锁&#xff0c;也称为记录锁&#xff0c;顾名思义就是在记录上加的锁。 注意&#xff1a; InnoDB行锁是通过给索引上的索引项加锁来实现的&#xff0c;而不是给表的行记录加锁实现的&#xff0c;这就意味着只有通过索引条件检索数据&#xff0c;InnoDB才使用行级锁…

【开发工具】git服务器端安装部署+客户端配置

自己安装一个轻量级的git服务端&#xff0c;仅仅作为代码维护&#xff0c;尤其适合个人代码管理。毕竟代码的版本管理是很有必要的。 这里把git服务端部署在centos系统里&#xff0c;部署完成后可以通过命令行推拉代码&#xff0c;进行版本和用户管理。 一、服务端安装配置 …

【Kubernetes】k8s--安全机制

机制说明 Kubernetes 作为一个分布式集群的管理工具&#xff0c;保证集群的安全性是其一个重要的任务。API Server 是集群内部各个组件通信的中介&#xff0c; 也是外部控制的入口。所以 Kubernetes 的安全机制基本就是围绕保护 API Server 来设计的。 比如 kubectl 如果想向 …

新版FMEA培训内容中关于团队协作的部分可以怎么展开?

团队协作&#xff0c;作为新版FMEA的核心要素之一&#xff0c;其重要性不言而喻。在FMEA的分析过程中&#xff0c;团队成员的密切合作与沟通是确保分析全面性和准确性的关键。通过团队协作&#xff0c;不同领域的专家能够共同参与到潜在故障模式的识别、评估与预防中来&#xf…

解决ubuntu22.04共享文件夹问题

刚开机发现ubuntu里面的共享文件夹访问不了了 ubuntuwxy:/mnt/hgfs$ ls找了几篇博客&#xff0c;设置如下指令即可&#xff0c;记得退出当前目录重新进入刷新一下 sudo vmhgfs-fuse .host:/ /mnt/hgfs/ -o allow_other -o uid1000 仅供参考

针对indexedDB的简易封装

连接数据库 我们首先创建一个DBManager类&#xff0c;通过这个类new出来的对象管理一个数据库 具体关于indexedDB的相关内容可以看我的这篇博客 indexedDB class DBManager{}我们首先需要打开数据库&#xff0c;打开数据库需要数据库名和该数据库的版本 constructor(dbName,…

[WTL/Win32]_[中级]_[MVP架构在实际项目中应用的地方]

场景 在开发Windows和macOS的界面软件时&#xff0c;Windows用的是WTL/Win32技术&#xff0c;而macOS用的是Cocoa技术。而两种技术的本地语言一个主打是C,另一个却是Object-c。界面软件的源码随着项目功能增多而增多&#xff0c;这就会给同步Windows和macOS的功能造成很大负担…

Aigtek高压放大器在柔性爬行机器人驱动性能研究中的应用

实验名称&#xff1a;柔性爬行机器人的材料测试 研究方向&#xff1a;介电弹性体的最小能量结构是一种利用DE材料的电致变形与柔性框架形变相结合设计的新型柔性驱动器&#xff0c;所谓最小能量是指驱动器在平衡状态时整个系统的能量最小&#xff0c;当系统在外界的电压刺激下就…

开发一个python工具,pdf转图片,并且截成单个图片,然后修整没用的白边

今天推荐一键款本人开发的pdf转单张图片并截取没有用的白边工具 一、开发背景&#xff1a; 业务需要将一个pdf文件展示在前端显示&#xff0c;但是基于各种原因&#xff0c;放弃了h5使用插件展示 原因有多个&#xff0c;文件资源太大加载太慢、pdf展示兼容性问题、pdf展示效果…

应急便携式气象观测站

TH-BQX5自然灾害&#xff0c;如台风、暴雨、洪涝、干旱等&#xff0c;给人们的生命财产安全带来了巨大威胁。在应对这些灾害时&#xff0c;准确的气象观测数据是制定有效应对策略的基础。近年来&#xff0c;应急便携式气象观测站在自然灾害的监测和预警中发挥了越来越重要的作用…