数据结构二叉树——堆

news2024/12/23 12:54:28

前言:哈喽小伙伴们,紧随上篇文章树的讲解,我们这篇文章开始进行二叉树的讲解。

先来看二叉树的一种特殊形式——堆。


目录

一.什么是堆

二.堆的概念

三.堆的实现

1.堆的创建

2.堆的销毁

 3.堆顶数据

4.堆的判空

5.堆的数据个数

 6.堆的插入

7.堆的删除

8.测试

四.完整代码展示

1.Heap.h

2.Heap.c

五.总结 


一.什么是堆

我们已经了解到,二叉树有顺序存储和链式存储两种方式。其中顺序存储比较特殊,它用数组来作为架构,这就要求树的各个节点之间必须是连续且有序,这样一来只有完全二叉树才符合条件,所以我们将顺序存储的二叉树另起一个新名字——


二.堆的概念

堆既然作为从二叉树独立出来的一个新的数据结构,自然要有它自己的特性:

  • 堆中的某个节点的值总是不大于或不小于其父节点的值。
  • 堆总是一棵完全二叉树。
  • 堆按照从上到下,从左到右的顺序依次在数组中排列。

如何理解第一个特性呢???来看下图:

  • 如果一个父节点的值比它的子节点的值都,那么我们将这样的堆称为小堆
  • 如果一个父节点的值比它的子节点的值都,那么我们将这样的堆称为大堆

三.堆的实现

我现在随便给出一个数组:

arr[] = { 9,4,6,2,7,1,8,4,8,2 };

该数组可以是一棵二叉树,但是不满足堆的条件,下面我们就来创建一个堆,并将该数组的数据按照堆的规则将它们入堆。(以小堆为例)


1.堆的创建

因为是数组型数据结构,所以一开始肯定还是要创建一个堆结构体:

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* data;
	int size;
	int capacity;
}Heap;

紧接着进行初始化:

// 堆的构建
void HeapCreate(Heap* hp)
{
	assert(hp);
	hp->capacity = 0;
	hp->size = 0;
	hp->data = NULL;
}

2.堆的销毁

销毁也是同样的操作:

// 堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->data);
	hp->capacity = 0;
	hp->size = 0;
	hp->data = NULL;
}

 3.堆顶数据

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	assert(hp->size > 0);
	return hp->data[0];
}

取堆顶数据是要断言堆是否为空。 


4.堆的判空

// 堆的判空
bool HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->size == 0;
}

堆判空比较简单,如果sizeof为0,就说明堆没有数据。 


5.堆的数据个数

// 堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}

数据个数即为size。


 6.堆的插入

整个入堆的操作分为两部分:

  1. 将数据存入堆底;
  2. 按照小堆的规则进行调整。

因为数据入堆的时候不一定就是按照小堆的规则,所以我们必须得有一个调整。

那么我们又该如何调整呢???

小堆的规则是,父节点的值不大于子节点的值,那如果新入堆的子节点的值要比父节点小,就将它们两个的值进行交换。交换之后,新的父节点还要和它的父节点再进行比较,如果小,那么就还要继续进行交换,就这样一步一步的向上调整

首先小伙伴们要知道,堆中的节点的序号和数组一样,根节点为0,依次往下排序

然后是一个很重要的小技巧,那就是,假设一个父节点的序号为N,它的其中一个子节点的序号为n,那么N = (n - 1)/ 2

通过这一点,我们就可以很容易的实现调整啦。

先来看数据入堆:

//堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	if (hp->size == hp->capacity)
	{
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(hp->data,sizeof(HPDataType)*newcapacity);
		if (tmp == NULL)
		{
			perror("HeapPush->malloc");
			exit(-1);
		}
		hp->data = tmp;
		hp->capacity = newcapacity;
	}
	hp->data[hp->size] = x;
	hp->size++;
	AdjustUp(hp->data, hp->size - 1);
}

入堆之后,开始进行调整,我们另建一个向上调整函数,并传入数组的地址和子节点的序号。

//交换
void Swap(HPDataType* x, HPDataType* y)
{
	HPDataType tmp = *x;
	*x = *y;
	*y = tmp;
}
//堆向上调整
void AdjustUp(HPDataType* a, int child)
{
	while (child > 0)
	{
		int parent = (child - 1) / 2;
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
		}
		else
		{
			break;
		}
        child = parent;
		parent = (parent - 1) / 2;
	}
}

因为需要进行交换,所以我们干脆就在写一个交换函数,这样更加方便。

在该函数中,我们首先要记录父节点的序号,然后通过while循换来实现一步一步的向上调整,值得注意的是while循环的条件:child > 0因为很有可以一直向上调整,直到该子节点成为最上边的根节点,如此一来,它的序号就是0。所以只要它还大于0 ,那么就反复执行循环

随后进行比较判断,如果子节点要小于父节点,就进行交换,然后让两个child和parent分别向上继承序号

反之,就不用交换,更不用再执行循环,所以就直接用break打破循环

如此一来我们就实现了堆的插入。


7.堆的删除

有小伙伴会说,堆的删除简单啊,直接size--。但是我要告诉你的是,谁说过堆的删除就是删除最后一个节点了???如果是这样,那岂不是太简单了。

所以实际上说堆的删除,都是删除它的根节点

这就很难办啊,根节点该怎么删除啊???

这时候又有小伙伴说,直接让数据都向前覆盖呗,话是这样说,但是假如说,我们一个小堆是1,3,2,4,5,把根节点1删除,剩下的序列是3,2,4,53成为新的根节点,但是它的子节点2比它小,这样就不满足小堆的规则了,堆的数据就全乱套了。

那么堆到底该如何进行根节点的删除呢???

既要满足把根节点删除,又要满足不破坏堆的结构和规则,为此诞生出了,先将根节点的值和最后一个节点的值交换,再让新根节点进行向下比较调整的方法。

//堆的删除
void HeapPop(Heap* hp)
{
	assert(hp);
	assert(hp->size > 0);
	Swap(&hp->data[0], &hp->data[hp->size - 1]);
	hp->size--;
	AdjustDown(hp->data, hp->size, 0);
}

首先,要断言堆是否为空

接着,我们将根节点与最后一个节点进行值的交换,再让size--,这样便满足的将根节点的值进行了删除

但是新的根节点的值不一定满是小堆的规则它可能比它的子节点的值更大,所以就要开始进行向下调整啦

//堆向下调整
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[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

向下调整,我们需要传入数组,数组的大小,以及根节点的序号0

通过公式N = (n - 1)/ 2的逆向操作,我们可以通过父节点的序号来得到它的左子节点的序号,因为要满足小堆的规则,所以向下调整时,我们要找两个子节点中值更小的一个来进行交换。 

所以我们首先要进行一次比较,如果右节点的值大于左节点,就让child++,这样child就是右节点的序号

紧接着就开始比较两个父子节点的值的大小,如果父比子大,就交换,并向下继承序号,反之就打破循环,结束调整

最后我们再来分析while循环的判断条件(child < size)如果一直向下调整,就会出现parent成为了最后一层的节点,那么他就没有子节点可以调整了,也就是它的child的序号已经超出size的范围了,所以循环便不再进行

此外,还有一种特殊情况:

如果此时的父节点为我们的红箭头指向的节点,那么他就只有左子节点,这样我们就不用比较左右节点的大小啦,所以判断左右节点的大小时,需要多加一条child+1 < size,来排除此特殊情况。


8.测试

#include "Heap.h"

void test(Heap* hp)
{
	HeapCreate(hp);
	HPDataType arr[] = { 9,4,6,2,7,1,8,4,8,2 };
	int size = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < size; i++)
	{
		HeapPush(hp, arr[i]);
	}
	while (!HeapEmpty(hp))
	{
		printf("%d ", HeapTop(hp));
		HeapPop(hp);
	}
}
int main()
{
	Heap hp;
	test(&hp);
	return 0;
}

因为堆同样不支持直接遍历的操作,所以还是要自己来实现遍历的操作,结果如下:

如果想要改为大堆,我们只需要将父子节点的大小比较和兄弟节点的大小比较进行更改即可。

大堆结果如下:


四.完整代码展示

1.Heap.h

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

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* data;
	int size;
	int capacity;
}Heap;

// 堆的构建
void HeapCreate(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);

2.Heap.c

#include "Heap.h"

// 堆的构建
void HeapCreate(Heap* hp)
{
	assert(hp);
	hp->capacity = 0;
	hp->size = 0;
	hp->data = NULL;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->data);
	hp->capacity = 0;
	hp->size = 0;
	hp->data = NULL;
}
//交换
void Swap(HPDataType* x, HPDataType* y)
{
	HPDataType tmp = *x;
	*x = *y;
	*y = tmp;
}
//堆向上调整
void AdjustUp(HPDataType* a, int child)
{
	while (child > 0)
	{
		int parent = (child - 1) / 2;
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
		}
		else
		{
			break;
		}
		parent = (parent - 1) / 2;
		child = (child - 1) / 2;
	}
}
//堆向下调整
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[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	if (hp->size == hp->capacity)
	{
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(hp->data,sizeof(HPDataType)*newcapacity);
		if (tmp == NULL)
		{
			perror("HeapPush->malloc");
			exit(-1);
		}
		hp->data = tmp;
		hp->capacity = newcapacity;
	}
	hp->data[hp->size] = x;
	hp->size++;
	AdjustUp(hp->data, hp->size - 1);
}
//堆的删除
void HeapPop(Heap* hp)
{
	assert(hp);
	assert(hp->size > 0);
	Swap(&hp->data[0], &hp->data[hp->size - 1]);
	hp->size--;
	AdjustDown(hp->data, hp->size, 0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	assert(hp->size > 0);
	return hp->data[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}
// 堆的判空
bool HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->size == 0;
}

五.总结 

堆最难的点就再于插入和删除,需要考虑父子节点之间的大小关系,但是如果能够理清思绪,问题自然也就迎刃而解啦。

最后喜欢博主文章的小伙伴记得一键三连支持一下哦!!!

我们下期再见啦!!!

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

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

相关文章

itop4412移植lrzsz工具踩坑笔记

4412开发板在传输文件一直用的都是tftp文件传输&#xff0c;但这样效率有点慢&#xff0c;平常在linux上习惯用lrzsz工具来传输文件&#xff0c;特此记录下&#xff0c;因为不熟悉linux编译 踩坑了很多地方 在操作前 我们的虚拟机要线安装好编译环境 下载lrzsz源码&#xff0…

CSS新手入门笔记整理:CSS基本选择器

id属性 id属性具有唯一性&#xff0c;也就是说&#xff0c;在一个页面中相同的id只能出现一次。在不同的页面中&#xff0c;可以出现两个id相同的元素。 语法 <div id"text"> ...... </div> class属性 class&#xff0c;顾名思义&#xff0c;就是“类…

医疗机构最核心的资源是什么?如何利用去获取客流?

核心资源一&#xff1a;医生——直接影响医疗机构口碑声誉 核心资源二&#xff1a;效率——直接影响患者满意度 医生为患者提供专业医疗服务的同时&#xff0c;也得确保高效地处理患者就诊需求&#xff0c;这是保障机构的服务质量和长久发展的根本。 那么该如何有效提升资源的…

如何处理枚举类型(上)

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 我们会分为上、下两篇分…

HCIA-RS基础-RIP路由协议

前言&#xff1a; RIP路由协议是一种常用的距离矢量路由协议&#xff0c;广泛应用于小规模网络中。本文将详细介绍RIP路由协议的两个版本&#xff1a;RIPv1和RIPv2&#xff0c;并介绍RIP的常用配置命令。通过学习本文&#xff0c;您将能够掌握RIP协议的基本原理、RIPv1和RIPv2的…

福州大学《嵌入式系统综合设计》实验七:图像灰度直方图

一、实验目的 直方图是一种统计特征&#xff0c;在图像中广为使用&#xff0c;因为具有计算简便、不受平移、旋转的影响&#xff0c;因此可以作为图像的一种有效的局部/全局特征来表示图像&#xff0c;是图像的重要特征之一。直方图在SIFT算法、HOG算法、直方图均衡等图像特征…

【阿里云】图像识别 智能分类识别 增加网络控制功能点(三)

一、增加网络控制功能 实现需求TCP 心跳机制解决Soket异常断开问题 二、Linux内核提供了通过sysctl命令查看和配置TCP KeepAlive参数的方法。 查看当前系统的TCP KeepAlive参数修改TCP KeepAlive参数 三、C语言实现TCP KeepAlive功能 四、setsockopt用于设置套接字选项的系…

为什么别人能做好CSGO游戏搬砖,而你不能?

CSGO搬砖日常出货更新 做Steam游戏搬砖的门槛很低&#xff0c;以至于这两年不断有小白涌入市场&#xff0c;想要在饰品市场中分一杯羹。这个项目是很简单&#xff0c;但想要站稳脚跟&#xff0c;有稳定收入的第一步就得搞清楚项目逻辑。 首先你得搞清楚&#xff0c;steam搬砖盈…

【MySQL系列】PolarDB入门使用

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

基于孔雀算法优化概率神经网络PNN的分类预测 - 附代码

基于孔雀算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于孔雀算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于孔雀优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

巧妙之中见真章:深入解析常用的创建型设计模式

设计模式之创建型设计模式详解 一、设计模式是什么&#xff1f;二、模板方法2.1、代码结构2.2、符合的设计原则2.3、如何扩展代码2.4、小结 三、观察者模式3.1、代码结构3.2、符合的设计原则3.3、如何扩展代码3.4、小结 四、策略模式4.1、代码结构4.2、符合的设计原则4.3、如何…

计算机视觉面试题-01

计算机视觉面试通常涉及广泛的主题&#xff0c;包括图像处理、深度学习、目标检测、特征提取、图像分类等。以下是一些可能在计算机视觉面试中遇到的常见问题&#xff1a; 图像处理和计算机视觉基础 图像是如何表示的&#xff1f; 图像在计算机中可以通过不同的表示方法&…

Leetcode—28.找出字符串中第一个匹配项的下标【简单】

2023每日刷题&#xff08;四十&#xff09; Leetcode—28.找出字符串中第一个匹配项的下标 实现代码 int strStr(char* haystack, char* needle) {int len1 strlen(haystack);int len2 strlen(needle);int idx -1;int i 0;while(i < len1 - len2) {if(strncmp(haystac…

尺度为什么是sigma?

我们先看中值滤波和均值滤波。 以前&#xff0c;我认为是一样的&#xff0c;没有区分过。 他们说&#xff0c;均值滤波有使图像模糊的效果。 中值滤波有使图像去椒盐的效果。为什么不同呢&#xff1f;试了一下&#xff0c;果然不同&#xff0c;然后追踪了一下定义。 12345&…

从程序员到解决方案工程师:一次跨界的职场冒险

在科技行业里&#xff0c;程序员和解决方案工程师是两个非常常见的职业。虽然这两个职业都需要一定的行业理解和问题解决能力&#xff0c;但它们的工作内容和职责却有很大的不同。 那么&#xff0c;如果一名程序员决定转行做一名解决方案工程师&#xff0c;他会经历怎样的体验…

QXDM Filter使用指南

QXDM Filter使用指南 1. QXDM简介2 如何制作和导入Filter2.1 制作Filter2.1.1 制作Windows环境下Filter2.1.2 制作Linux环境下Filter 2.2 Windows环境下导入Filter 3 Filter配置3.1 注册拨号问题3.1.1 LOG Packets(OTA)3.1.2 LOG Packets3.1.3 Event Reports3.1.4 Message Pack…

Java网络爬虫实战

List item 文章目录 ⭐️写在前面的话⭐️&#x1f4cc;What is it?分类网络爬虫按照系统结构和实现技术&#xff0c;大致可以分为以下几种类型&#xff1a;通用网络爬虫&#xff08;General Purpose Web Crawler&#xff09;、聚焦网络爬虫&#xff08;Focused Web Crawler&a…

关于python中的nonlocal关键字

如果在函数的子函数中需要调用外部变量&#xff0c;一般会看见一个nonlocal声明&#xff0c;类似下面这种&#xff1a; def outer_function():x 10def inner_function():nonlocal xx 1print(x)inner_function()outer_function()在这个例子中&#xff0c;inner_function 引用…

AR眼镜双目光波导/主板硬件方案

AR(增强现实)技术的发展离不开光学元件&#xff0c;而在其中&#xff0c;光波导和Micro OLED被视为AR眼镜光学方案的黄金搭档。光学元件在AR行业中扮演着核心角色&#xff0c;其成本高昂且直接影响用户体验的亮度、清晰度和大小等因素。AR眼镜的硬件成本中&#xff0c;光机部分…

Postman如何使用(二):Postman Collection的创建/使用/导出分享等

一、什么是Postman Collection&#xff1f; Postman Collection是可让您将各个请求分组在一起。 您可以将这些请求组织到文件夹中。中文经常将collection翻译成收藏夹。如果再下文中看到这样的翻译不要觉得意外。Postman Collection会使你的工作效率更上一层楼。Postman Colle…