你真的会数据结构吗:堆

❀❀❀ 文章由@不准备秃的大伟原创 ❀❀❀

♪♪♪ 若有转载,请联系博主哦~ ♪♪♪

❤❤❤ 致力学好编程的宝藏博主,代码兴国!❤❤❤

        好久不见,甚是想念,不知道大家有没有察觉到大伟的头像和名字变了鸭 <(* ̄▽ ̄*)/ ,哈哈,大伟现在是伟大啦,哈哈!

        前一周大伟雀食是很忙啊,课很多,但是这周也算是忙里偷闲了,久违的来更一更博客,哎呀,不过太久没来碰博客,大伟都不清楚自己写到哪里了,大家为何不帮我看看上一期的内容呢? ○( ^皿^)っHiahiahia…  ------->队列

         哦哦,没错,我们上次学到队列了,嘿嘿,那我们就继续数据结构的学习:树。

        那这时候就有人好奇了,这个树是个啥啊,这树长得千奇百怪的,有啥规律啊?诶,别急嘛,那学习树之前,我们还得来了解一下树是什么类型的结构:

  树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

        长啥样?上图! 

 3f431f9bdea04d85baaecaa41ff7a66b.png

        不知道有没有眼睛好的同学发现 ,上面这棵树上的是不是有些很标致啊?没错,这棵树的每个节点(还是说树枝)都是左右分叉的,那这个图片里的树和我们的数据结构的数有什么关联呢?诶,这关联可大了,简单说,把这个照片反过来就是我们的数,还是最经典的二叉树哦~

        哈哈,这么说大家可能还是不理解,那来看看我们数据结构里面树的逻辑结构图吧:92282a2be221450abe8522f9f04addfd.png

        相信凭大家的水平,不需要大伟多说也能理解了吧,好,本篇博客到此为止,谢谢大家观看~

——————————————————————————————————————————

        嘿嘿~ 简单说一下树的各种定义吧:

  1.   最上面的节点,称作根节点(树根) 
  2. 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为2
  3. 叶节点或终端节点:度为0的节点称为叶节点; 如上图:H、I、J、F..等节点为叶节点 非终端节点或分支节点:度不为0的节点; 如上图:D、E、B、C...等节点为分支节点
  4. 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
  5. 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
  6. 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
  7. 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为2
  8. 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  9. 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
  10. 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:D、F互为堂兄弟节点
  11. 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
  12. 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
  13. 森林:由m(m>0)棵互不相交的树的集合称为森林;

        此外,大伟还要多嘴一句,子树之间不能有交集,因为那样的话就成为了 “图” ,这也是我们后面要学的,不过现在可以不用管。

        那接下来我们开始实现一下树吧,那实现什么样子的树呢?就上面图片里面的那样就可以咯,那我们还给这种特殊的数起了个专门的名字: 二叉树,是不是听名字就能想到样子呢,哈哈。

        万事开头难,我们要用什么方法实现二叉树呢?其实这个问题前人也想了很多方法,不过最后我们选用的也是现在最流行的一种方法,我们称之为:孩子兄弟表示法

        那何为孩子兄弟表示法呢?简单来说,就是上面的定义(嘿嘿( >ρ < ”))

        树,我们要实现什么功能呢?一个结构不仅要好看,也需要有用,那我们树有什么用呢?

        其实我知道的,大家应该都有好奇的地方,大伟的标题写的是堆,但是前面的内容都是介绍的数,是不是大伟学傻了,糊涂了?当然不是,哈哈,那还得了。其实我们数的实现就是以堆为基础的,简单说,树就是在堆的基础之上搭建的,所以我们这一篇要实现的内容是:

        那堆的性质有啥呢?

  一.堆中某个节点的值总是不大于或不小于其父节点的值;

  二.堆总是一棵完全二叉树。

         根据性质一,我们还可以把堆分为大堆和小堆,那大堆和小堆分别是什么意思呢?

        大堆:在二叉树的逻辑结构下,上面一层总是比下面一层的数据(父亲总是比子女)大

        小堆:在二叉树的逻辑结构下,上面一层总是比下面一层的数据(父亲总是比子女)小

        上逻辑图(以小堆为例子): 333064455f1a43f1a1aa0b65ee303b86.png

        如上图,那堆的逻辑结构是啥样的?和二叉树一个样,那物理结构呢?数组。

        好,那我们要实现什么接口呢?

        首先是堆的创建,没话说,然后就是需要把当前的堆的物理结构打印出来,此外,既然我们都分了大小堆,于是我们就需要实现大小堆的接口,此外,如果没有数据也无法成堆,于是我们需要有一个插入的接口,光增不删也不行是吧,于是就来一个删除,然后就是取堆顶(根),堆判空,堆的数据个数,销毁堆。暂时也就这些接口了,好,开码!

        Heap.h

        结构体:需要一个数组,一个堆的最大容量,堆的目前容量(和顺序表一样的定义) 

typedef int HpDataType;
typedef struct Heap
{
	int* a;
	int sz;
	int capacity;
}Heap;

        接着就是接口的声明:

// 堆的构建
void HeapCreate(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HpDataType x);
//向上调整
void AdjustUp(HpDataType* a, int child);
//向下调整
void AdjustDown(HpDataType* a,int n, int child);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HpDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);

        Heap.c

                 堆的创建:

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

	hp->a = NULL;
	hp->capacity = hp->sz = 0;
}

                堆的销毁:

void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->a);
	hp->a = NULL;
	hp->capacity = hp->sz = 0;
}

                向上调整:

        可能会有铁汁好奇,这个向上调整是什么,还记得孩子兄弟法吗,就是当我们插入一个数据(尾部)的时候,我们不知道这个数据的大小,比如小堆的时候,若我们插入的数据比较小,需要进入到中间的某个位置,那我们这个时候就需要向上调整,当然了,逻辑结构上就是这个数依次网上交换数据,直到到合适的位置上,也就是说满足小堆的定义。

        那物理结构上怎么看呢?其实也不难,就是一个数组的下这边搞搞,那边搞搞。具体咋搞捏,如下:5115c2f14ef54708b71e3a4febd1e67c.png

        就比如这个,如果我们想把16(下标为6)的数调到他的父亲节点去,怎么办,诶,根据逻辑结构:8b66b21f303d46ac82a3baaeacc0c0b1.png

        我们(6-1)/ 2 == 2 (整形),如果是吧81和15交换呢?(5-1)/ 2 == 2,还是可以得到父亲节点的坐标,其他的子树类推,这样就好搞了,如果孩子要往上调,就直接减一再除二,代码如下:

void AdjustUp(HpDataType* a, int child)
{
	int parent = (child - 1) / 2;//父亲节点
	while (child > 0)//当孩子节点下标有效
	{
//如果孩子节点比父亲节点小,就交换
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
//迭代两个节点
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

        当然了,向下调整里面调用了交换函数,相信大家已经如张飞吃豆芽,小菜一碟了吧,

void Swap(HpDataType p1,HpDataType p2)
{
	HpDataType  tmp = p1;
	p1 = p2;
	p2 = tmp;
}

        好,给兄弟们出个问题:上面的交换函数写的对不对? 

        没问题啊,是吧?那我再问一句,出了Swap函数后,两个数的值真的交换了吗?

      这边就是考基础知识了,我们在函数内部交换了数据,但是呢,一出函数,栈帧销毁了,我们的值还是没有变化,一切都白费了,那咋解决?传地址呗,别忘了,C语言的灵魂,指针!于是我们就可以这么写:

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

                 插入数据:

void HeapPush(Heap* hp, HpDataType x)
{
	assert(hp);
//如果堆为空或者满了
	if (hp->capacity == hp->sz)
	{
		int newcapacity = hp->capacity == 0 ? 4 : 2 * (hp->capacity);
		HpDataType* tmp = realloc(hp->a, newcapacity * sizeof(int));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		hp->a = tmp;
		hp->capacity = newcapacity;
	}

	hp->a[hp->sz] = x;
	hp->sz++;
//向上调整,记住是下标,所以sz要-1
	AdjustUp(hp->a, hp->sz-1);
}

                那啥时候需要向上调整啊?仔细想想,我们插入数据是向下调整,那么删除数据不就是向上调整了吗?没错,那我们如何删数据呢?有很多办法,但是大伟也只讲其中的一种,先说一下思想:

将堆顶(根)和最后一个节点交换,此时直接size--,这个时候的堆顶(根)是之前的比较大的值,然后我们将根依次向下调整,直到位于合适的位置,此时还是个小堆。

        那么物理结构呢?其实和向上调整很像,不同的就是父子节点交换,简单看一下逻辑结构:

8b66b21f303d46ac82a3baaeacc0c0b1.png         还是这张图,如果我们要把15(下标为2)的值调到81(位置为5)该怎么调,直接2*2+1就可以了,但是如果我们将这两个值交换后,出现了新的问题:此刻的下标为2位置的值比下标为6位置的值要大,这样就不是小堆了,于是,向下调整在调整前还需要一些判断。如果左孩子大于右孩子,我们就让孩子加一,让父亲与右孩子交换,但是,其中有一个小细节,我们的堆不一定是完全二叉树,也就是我们不一定有右孩子,所以,还需要判断右孩子存在,代码如下:

                向下调整:

void AdjustDown(HpDataType *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[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}

                删除数据:

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

//根据思想,首尾交换
	Swap(&hp->a[0], &hp->a[hp->sz - 1]);
	hp->sz--;

//第二个传的是n,所以不需要-1!
	AdjustDown(hp->a,hp->sz, 0);

}

                返回堆的大小:

int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->sz;
}

                 返回堆顶元素:

HpDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->a[0];
}

                堆判空:

bool HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->sz == 0;
}

        这样,我们的小堆就基本实现完成了,简单写一个案例,赶紧来试试吧! 

        test.c

int main()
{

	int a[] = { 60,70,65,50,32,100 };

	Heap hp;
	HeapCreate(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
//入堆
		HeapPush(&hp, a[i]);
	}

	printf("%d\n", HeapTop(&hp));//32
	HeapPop(&hp);//删除32
	printf("%d\n", HeapTop(&hp));//50
	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp));//50 60 65 70 100
		HeapPop(&hp);
	}

	HeapDestory(&hp);
	return 0;
}

        咱们来看看答案是不是和我们的预想一样呢:563f15ee18a84fc8a9df43fecd90e562.png

        对比一下,一模一样,这样我们的小堆也就实现完成了!

        诶,这个时候就有铁汁好奇了,那既然小堆实现完成了,那么大堆呢?嘿,这不用很简单?直接将向下调整和向上调整的孩子节点的小于父亲节点改为大于,将父亲节点的大于孩子节点改为小于,还有向下调整的左孩子小于右孩子改为大于不就好了?(◡ᴗ◡✿)

        那大伟改一下,来看看? 

 e47fe826b7894198bf56aedee291b816.png

        由于思想差不多,这里大伟就不过多赘述,大家自己理解哦~

        OK,那咱们的堆到这里也就完成了,接下来就是真正的二叉树,还有排序啦!希望大家继续支持大伟,加油!*(੭*ˊᵕˋ)੭*ଘ

        相信自我是成功的基石,完善自我是成功的阶梯,突破自我是成功的钥匙,合谋共处是成功的翅膀,确立目标是成功的起点,付注行动是成功的号角。 

         本篇博客也就到此为止了,送大家一碗鸡汤,勉励自己以及这世界上所有追逐梦想的赤子趁年华尚好努力提升自己,莫欺少年穷!   a4927d4c455f4f3f8a72737158a5dc3d.jpeg

 

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

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

相关文章

RK3568驱动指南|第十三篇 输入子系统-第151章 通用事件处理层read和write函数分析

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

Leetcode第26题:删除有序数组中的重复项

代码实现 注意:该题要求原地删除&#xff0c;不能引入额外的连续内存空间 class Solution:def removeDuplicates(self, nums: List[int]) -> int:not_sorted_lengthlen(nums)while(not_sorted_length>0):numnums.pop(0)not_sorted_length-1if num not in nums:nums.appe…

【二十三】【算法分析与设计】三柱汉诺塔详解,计算子移动次数,正常递归计算,观察数据得出数学规律,递归图得出数学规律,将递归函数转化为递推式

目录 汉诺塔递归 汉诺塔子移动次数的计算 牛牛的汉诺塔 选择正常的递归模拟计算子移动次数 根据具体数据得出数学规律 根据递归图得出数学规律 将递归函数转化为递推式 结尾 汉诺塔递归 汉诺塔是一个经典问题&#xff0c;相传在古印度圣庙中&#xff0c;有一种被称为汉…

【框架】说一说 Fork/Join?

SueWakeup 个人主页&#xff1a;SueWakeup 系列专栏&#xff1a;学习Java框架 个性签名&#xff1a;人生乏味啊&#xff0c;我欲令之光怪陆离 本文封面由 凯楠&#x1f4f7; 友情赞助 目录 前言 什么是 Fork&#xff1f; 什么是 Join&#xff1f; Fork/Join 的核心组件 F…

基于K-近邻的PLOSAR图像分类

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

网络原理(6)——IP协议

目录 一、网段划分 现在的网络划分&#xff1a; 1、一般情况下的家庭网络环境 2、IP地址 3、子网掩码 4、网关 以前的网络划分&#xff1a; 二、特殊IP 1、环回 IP 2、主机号为全 0 的IP 3、广播地址IP 三、路由选择&#xff08;路线规划&#xff09; 一、网段划分…

智慧城管综合执法办案系统,现场移动执法APP源码,占道经营AI智能识别分析系统

智慧城管执法平台源码 智慧城管综合执法办案系统&#xff0c;提供了案件在线办理、当事人信用管理、文书电子送达、沿街店铺分析等功能&#xff0c;全面赋能执法队员&#xff0c;提高执法队员办案效率。 智慧城管综合执法办案系统在业务上能够支持所有行政处罚权力项目的网上运…

systrace抓取

1. 抓取systrace日志 adb root adb shell atrace -z -b 8192 video gfx input view wm rs hal sched freq idle irq -t 10 > /sdcard/trace_output atrace: Android Trace命令&#xff0c;用于在Android系统上进行性能跟踪和分析。 -z: 压缩跟踪数据&#xff0c;减小输出文…

Excel中最常用的快捷健,每天都会用到

Hello&#xff0c;大家好&#xff0c;今天跟大家分享我们工作中经常使用的快捷键&#xff0c;快捷键能够在一定程度上提高我们的工作效率&#xff0c;快速达到我们想要的结果&#xff0c;善用快捷键也能让别人觉得你非常的厉害。 1快速求和 &#xff1a;Alt 使用方法非常的简…

Python编程异步爬虫实战案例

aiohttp异步爬取实战 案例介绍 链接为https://spa5.scrape.center&#xff0c;页面如下图所示&#xff1a; 这是一个图书网站&#xff0c;整个网站包含数千本图书信息&#xff0c;网站数据是JavaScript渲染而得的&#xff0c;数据可以通过Ajax接口获取&#xff0c;并且接口没…

深度学习 - PyTorch基本流程 (代码)

直接上代码 import torch import matplotlib.pyplot as plt from torch import nn# 创建data print("**** Create Data ****") weight 0.3 bias 0.9 X torch.arange(0,1,0.01).unsqueeze(dim 1) y weight * X bias print(f"Number of X samples: {len(…

ZYNQ学习之Ubuntu环境下的Shell与APT下载工具

基本都是摘抄正点原子的文章&#xff1a;<领航者 ZYNQ 之嵌入式Linux 开发指南 V3.2.pdf&#xff0c;因初次学习&#xff0c;仅作学习摘录之用&#xff0c;有不懂之处后续会继续更新~ 一、Ubuntu Shell操作 简单的说Shell 就是敲命令。国内把 Linux 下通过命令行输入命令叫…

代码随想录算法训练营第三十二天 | 122.买卖股票的最佳时机II ,55. 跳跃游戏 , 45.跳跃游戏II

贪心&#xff1a;只要把每一个上升区间都吃到手&#xff0c;就能一直赚 class Solution { public:int maxProfit(vector<int>& prices) {int res 0;for(int i 1;i< prices.size();i){int diff prices[i] - prices[i-1];if(prices[i] > prices[i-1]){res d…

WSL使用

WSL使用 WSL安装和使用 Termianl和Ubuntu的安装 打开Hype-V虚拟化配置Microsoft Store中搜索Window Terminal并安装Microsoft Store中搜索Ubuntu, 选择安装Ubuntu 22.04.3 LTS版本打开Window Terminal选择Ubuntu标签栏, 进入命令行 中文输入法安装 查看是否安装了fcitx框架…

【官方】操作指南,附代码!银河麒麟服务器迁移运维管理平台V2.1中间件及高可用服务部署(4)

1.RocketMQ集群模式 主机配置示例&#xff1a; IP 角色 架构模式 对应配置文件 1.1.1.1 nameserver1 master broker-n0.conf 2.2.2.2 nameserver2 salve1 broker-n1.conf 3.3.3.3 nameserver3 salve2 broker-n2.conf 1.1.安装rocketmq 在服务器上安装rocket…

第14篇:2线-4线译码器

Q&#xff1a;有编码器那对应的就会有译码器&#xff0c;本期我们来设计实现2线-4线二进制译码器 。 A&#xff1a;基本原理&#xff1a;译码器是编码器的逆过程&#xff0c;其功能是将具有特定含义的二进制码转换为对应的输出信号。2线-4线二进制译码器有2个输入共4种不同的组…

java目标和(力扣Leetcode106)

目标和 力扣原题 问题描述 给定一个正整数数组 nums 和一个整数 target&#xff0c;向数组中的每个整数前添加 ‘’ 或 ‘-’&#xff0c;然后串联起所有整数&#xff0c;可以构造一个表达式。返回可以通过上述方法构造的、运算结果等于 target 的不同表达式的数目。 示例 …

【MySQL】11. 复合查询(重点)

4. 子查询 子查询是指嵌入在其他sql语句中的select语句&#xff0c;也叫嵌套查询 4.1 单行子查询 返回一行记录的子查询 显示SMITH同一部门的员工 mysql> select * from emp where deptno (select deptno from emp where ename SMITH); -----------------------------…

小目标检测篇 | YOLOv8改进之添加BiFormer注意力机制

前言:Hello大家好,我是小哥谈。BiFormer是一种具有双层路由的动态稀疏注意力机制,它通过查询自适应的方式关注一小部分相关标记,从而提供了更灵活的计算分配和内容感知。它在多个计算机视觉任务中表现出了良好的性能和高计算效率。BiFormer注意力机制比较适合处理小尺度目标…

聚类算法之高斯混合模型聚类 (Gaussian Mixture Model, GMM)

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 高斯混合模型&#xff08;GMM&#xff09;是统计模型中的一颗璀璨之星&#xff0c;它为数据提供了一种复杂而又强大的表示方法。在机器学习的许多…