【C++】树?堆?怎么实现?

news2025/7/23 16:17:05

新的一周过去了,大家有没有对上星期练习的题目更加熟练呢?

上星期和上上星期我们主要学习了顺序表,链表,和用这俩都能实现的栈和队列

那么今天我们看看堆又是什么结构

目录

1.树 介绍

2.堆 介绍

3.堆的实现


1.树の介绍

不就是树,谁没见过对吧

那么类似的计算机也有一种树,很像我们学过的树状图

 

为了规范使用,先定义一些名称

层数高度如你所见 就是你理解的意思

其实我们发现,这个命名规则就很像遗传族谱,其中也是借用了遗传族谱的一些名称

我们可以类比理解一下兄弟节点堂兄弟节点父子节点。。。

节点的祖先,是指从该节点网上走的唯一路径上经历的所有节点都是他的  节点的祖先 

我们规定:树和树之间不能交叉!!!

森林:互不相交的树构成的结构~~

这只是最普通的树,还有一些稍微长得特殊一点的树

二叉树:每个父节点都有两个枝丫

 

二叉树里面又有两种

满二叉树:每一层都是满的节点

根据等比公式,满二叉树的高度为h(本图是4)那么节点个数就是2^h-1

第k层是满的,那么那一层的节点个数是2^(k-1) ,k从1开始 

 

完全二叉树:最后一层可以不满,但是前面层数必须满,必须从左到右连续

大家自己去算一下,和刚才的方法一样的

完全二叉树的高度是h,那么总结点最多2^h-1  最少2^(h-1) 


还有一些小小的二叉树计算技巧~

1.度为0的节点个数比度为2的节点个数多1

 2.完全二叉树度为1 的节点个数不是0就是1

3.要计算叶子节点个数的题目,就是s算度为0,可以假设度为0有N0个,度为1有N1,度为2有N2

再根据 前两个技巧建立方程就好了


 

简单的基础知识就是这么多,那么真的在内存中就是有一块空间存储这些小圈和直线吗?

当然不是!!!!!!

这个树形状的图只是帮助我们人理解的(人家计算机根本不用借助图好吧)

所以这个图就是一种假想的模型,术语叫做 逻辑结构!!!(逻辑不好得用他帮助理解)


那么就该思考了,真实的内存结构(术语:物理结构)是什么样子

对于堆 其实是数组~

更复杂的现在不讨论  


 那么对于一般的树,他的“遗传图谱”怎么表示出来啊~~~

有个大神,想到了一种很的方法

struct TreeNode
{
int data;
TreeNode* child;
TreeNode* brother; 
};

我们细想一下,如果从最开始的根,我知道他的  孩子节点  和 孩子节点的兄弟节点 

那简直就是完美解决整个图谱

 

直呼大佬 


 

2.堆的介绍

大家肯定在C的时候就对堆耳熟能详,现在我们主要看一下更深层次的理解

 刚才说过,

他的存放方式是这样的

从0开始编号,是为了和数组从0开始很好的对应

我们可以心里想着左图  但是不能不清楚他的真实结构是右图!!! 


一个随随便便的数组不一定是堆,但是堆一定是一个数组 

给你一个数组怎么判断他到底是不是一个堆?

首先我们要清楚堆的分类

大堆:每一个父节点都比他的子节点大

 

小堆:每一个父节点都比他的子节点小

所以我们只要看是不是大/小堆其中一种就行

比如数组{1,4,5,8,99} 1比4 5都小,4比8和99都小,所以这就是一个小堆,即堆

从中可以看出,对于数组你能不能一眼看出谁是谁的孩子,是左孩子还是右孩子?很重要

因为堆是完全二叉树 ,所以每个节点最多肯定是2个孩子

第一个数据1一定是根,往下两个4 5就是根(1)的孩子节点,在前面的4是左孩子

8,99是4,5中靠前数据(即4)的孩子节点,同样的8是左孩子


 

 对应上我们刚才学的父子节点,不能拿发现父子节点的下标其实有规律

左孩子下标=父亲下标*2+1    因为左孩子的下标总是奇数!!

右孩子下标=父亲下标*2+2    因为左孩子的下标总是偶数!!

父亲下标=(孩子(左/右)-1)/2


 

3. 堆的实现(重点重点)

 既然我们这么了解堆的结构,那么上手吧

这个实现和顺序表就很相似

还是定义一个结构体,也就是表示一个节点

a表示结构体指针,可以用指针的方式访问数组

size是数组中元素的个数

capacity是容量,当容量和元素个数相等的时候要考虑扩容(老生常谈了)

数据类型重定义可以方便以后存放不同类型的数据

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

//打印
void HeapPrint(Heap* hp);
//交换
void Swap(HPDataType* a, HPDataType* b);
// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n);
//向上调整
void AdjustUp(HPDataType* a, int chirld);
//向下调整
AdjustDown(HPDataType* a, int n, int parent);
//初始化
void HeapInit(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);
void PrintTopK(int* a, int n, int k);

主要的几个功能就是(按我的代码顺序)


 

打印(为了方便调试看结果的没什么含金量,大家自己就能完成)

//打印
void HeapPrint(Heap* hp)
{
	assert(hp);
	for (int i=0;i<hp->size;i++)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}

 

堆的初始化(指针a开不开空间都可以)

 

//初始化
void HeapInit(Heap* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->capacity = hp->size = 0;
}

 

堆的构建:给你一个杂乱的数组,把他变成堆的形式(以大堆为例)

首先肯定是把所给数组中的数据都拷贝到结构体变量hp的指针a指向的数组里面啊

由于我们在初始化指针a的时候是没有开空间的(开不开都行,我写的是没开的版本,开了的话要先考虑扩容问题)

想到memcpy

拷贝好之后就要考虑是不是符合我大堆的定义

也就是考虑

 这三个子树是不是符合父节点比子节点大的要求,可以向下调整的方法,把父节点下标传给一个向下调整的函数作为家长节点下标,把容量和指针他也传过去

向下调整函数

AdjustDown(HPDataType* a, int n, int parent)
{
	assert(a);
	int child = 2 * parent + 1;//假设是左孩子,并且左孩子是最大的孩子
	while (child <n)
	{
		if (a[child + 1] > a[child] && child+1<n)
		{
			child = child + 1;
		}
		//此时的child已经是最大的孩子
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			parent=child;
			child= 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

其中还复用了一个交换值的函数,由于后面交换的地方较多,最好还是交给函数去做

//交换
void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

 

我们发现我标记的1 2 3其实是连续的下标!! 

所以堆创建函数

// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n)
{
	assert(hp);
	hp->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (hp->a == NULL)
	{
		perror("HeapCreate fail");
		exit(-1);
	}
	memcpy(hp->a, a, n*sizeof(HPDataType));
	hp->size = hp->capacity=n;
	for (int i = (n - 1 - 1) / 2; i >=0; i--)
	{
		AdjustDown(hp->a, n, i);
	}
}

 


有创建肯定要有销毁,不然会内存泄漏

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

判断是不是空堆

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

堆的插入(尾插)

把一个接点放进去还要考虑这个节点可能会比他的父节点还大,所以他就要开始篡改族谱了...

和爸爸,爷爷...都比较一下,谁更大谁就在在上面,所以要一个函数:向上调整(还是以大堆为例)

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

 所以堆插就是

// 堆的插入,尾插
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	if (hp->capacity == hp->size)
	{
		//需要扩容
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		HPDataType* newnode = (HPDataType*)realloc(hp->a,sizeof(HPDataType) * newcapacity);
		if (newnode == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}
		hp->a = newnode;
		hp->capacity = newcapacity;
		
	}
        hp->a[hp->size] = x;
		hp->size++;
		//向上调整
		AdjustUp(hp->a, hp->size - 1);
}

堆的删除

不能简单覆盖,这样你千辛万苦的父子关系全乱套了

所以先把最后一个节点和根换一下,然后尾删直接把size--

之后就该考虑一下向下调整的问题

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

取堆顶的数据

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

堆数据个数

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

找出前k个最大 

//找出前k个最大
void PrintTopK(int* a, int n, int k)
{
	assert(a);
	Heap hp;
	while (k--)
	{
		HeapCreate(&hp, a, k);
	}
}

最后最后完整的实现在下面

include "Heap.h"

//打印
void HeapPrint(Heap* hp)
{
	assert(hp);
	for (int i=0;i<hp->size;i++)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}
//交换
void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

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

// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n)
{
	assert(hp);
	hp->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (hp->a == NULL)
	{
		perror("HeapCreate fail");
		exit(-1);
	}
	memcpy(hp->a, a, n*sizeof(HPDataType));
	hp->size = hp->capacity=n;
	for (int i = (n - 1 - 1) / 2; i >=0; i--)
	{
		AdjustDown(hp->a, n, i);
	}
}
//初始化
void HeapInit(Heap* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->capacity = hp->size = 0;
}

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

//向上调整,大堆
void AdjustUp(HPDataType* a, int child)
{
	assert(a);
	int parent = (child - 1) / 2;
	while (child>=0)
	{
		if (a[child] > a[parent])
		{
			Swap(&a[child],& a[parent]);
			child = parent;
			parent= (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
// 堆的插入,尾插
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	if (hp->capacity == hp->size)
	{
		//需要扩容
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		HPDataType* newnode = (HPDataType*)realloc(hp->a,sizeof(HPDataType) * newcapacity);
		if (newnode == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}
		hp->a = newnode;
		hp->capacity = newcapacity;
		
	}
        hp->a[hp->size] = x;
		hp->size++;
		//向上调整
		AdjustUp(hp->a, hp->size - 1);
}

//向下调整
AdjustDown(HPDataType* a, int n, int parent)
{
	assert(a);
	int child = 2 * parent + 1;//假设是左孩子,并且左孩子是最大的孩子
	while (child <n)
	{
		if (a[child + 1] > a[child] && child+1<n)
		{
			child = child + 1;
		}
		//此时的child已经是最大的孩子
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			parent=child;
			child= 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

// 堆的删除
void HeapPop(Heap* hp)
{
	assert(hp);
	assert(!HeapEmpty(hp));
	Swap(&hp->a[0], &hp->a[hp->size - 1]);
	hp->size--;
	AdjustDown(hp->a, hp->size, 0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}
//找出前k个最大
void PrintTopK(int* a, int n, int k)
{
	assert(a);
	Heap hp;
	while (k--)
	{
		HeapCreate(&hp, a, k);
	}
}

 


学会了吗??不懂的留言~

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

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

相关文章

cartopy绘制中国降雨地图

常用的地图可视化的编程工具有 MATLAB、IDL、R、GMT、NCL 等。相比于ArcGIS、QGIS和ArcGISpro用鼠标点来点去&#xff0c;编程绘图也是有很大的优点的&#xff0c;方便&#xff0c;可批量&#xff0c;美观。 大气科学和气象的朋友们一直使用的应该是 NCL&#xff0c;易用性不错…

Windows 编写自动复制备份、删除文件定时任务脚本

目录 一、backup.bat 脚本内容如下&#xff1a; 二、脚本内容解析 1.自动生成当天日期的目录 2. 删除前 n 天的文件 forfile 命令参数说明&#xff1a; 3.复制文件到指定目录 robocopy 命令参数说明&#xff1a; 结论&#xff1a; 三、设置定时任务 1. 打开 控制面板…

【数据结构】谈谈ArrayList和LinkedList的区别

&#xff08;此图源于比特高博&#xff09; 上图简洁明了的列出了二者的不同点 下面咱们详细聊聊具体的 要问的是区别&#xff0c;问不同点&#xff0c;那就得从二者共有的但是不同的点来讨论 1.底层实现上&#xff1a;ArrayList底层是顺序表&#xff0c;采用数组结构&…

引入DDP技术:英特尔网卡让数据处理更高效

英特尔网卡引入DDP技术后&#xff0c;提高了云和NFV部署的数据包处理效率&#xff0c;按需重配置报文处理引擎&#xff0c;让数据处理更高效 ◆可编程报文处理流水线 ◆按需优化工作负载 ◆无需重启服务器 ◆设备使用更高效 ◆无缝启用新服务 Intel Ethernet 700系列产品…

谷粒商城项目总结(一)-基础篇

一、项目简介 本项目适合人群&#xff1a;学过ssm是必须的。项目里有mybatis-plus和springcloud的内容&#xff0c;你可以用本项目来做实践&#xff0c;也可以利用本项目初识cloud&#xff0c;但最好还是对微服务有一定了解。 下好了vargant&#xff0c;如果安装centos7很慢&…

是什么让 NFT 项目成为“蓝筹”?

Nov. 2022, Vincy Data Source: Footprint Analytics - Bluechip Collection 在 NFT 这样一个不稳定和新兴的行业中&#xff0c;要赋予项目为 "蓝筹 " 地位是很难的。然而&#xff0c;不少的 NFT 项目宣称自己是蓝筹项目&#xff0c;但它们是吗&#xff1f; Foot…

从零开始配置vim(29)——DAP 配置

首先给大家说一声抱歉&#xff0c;前段时间一直在忙换工作的事&#xff0c;包括但不限于交接、背面试题准备面试。好在最终找到了工作&#xff0c;也顺利入职了。期间也有朋友在催更&#xff0c;在这里我对关注本系列的朋友表示感谢。多的就不说了&#xff0c;我们正式进入vim …

【案例 5-1】 模拟订单号生成

【案例介绍】 1.任务描述 编写一个程序&#xff0c;模拟订单系统中订单号的生成。例如给定一个包括年月日以及毫秒值的 数组 arr{2019,0504,1101},将其拼接成字符串 s:[201905041101]。要求使用 String 类常用方 法来实现字符串的拼接。 2.运行结果 运行结果如图 5-1 所示 图…

【SRE】Linux加入AD域控

老牌企业一般因为安全要求或者历史遗留要求&#xff0c;会要求服务器加入AD域控 RHEL/CentOS/Ubuntu 加入 Windows ldap 域控 网上有各种各样的方法&#xff0c;很多复杂且模糊&#xff0c;操作到一大半发现没法推进&#xff0c;这个是亲测最好用的办法 使用pbis-open使Linux服…

关于Ubuntu ssh远程连接报错和无法root登录的解决方法

一、使用远程工具连接Ubuntu提示报错 MobaXterm v22.0 版本直接可以远程连接上&#xff08;前提是sshd服务是开启的状态&#xff09; 注意&#xff1a;须使用最新版本或较高版本的ssh远程连接工具&#xff0c;进行ssh连接&#xff1b;若使用较低版本的ssh远程连接工具&#xf…

MySQL产生死锁原因

阅读目录锁类型介绍死锁产生原因和示例1、产生原因2、产生示例案例一案例二案例三案例四案例五案例六锁类型介绍 MySQL 有三种锁的级别&#xff1a;页级、表级、行级 1 表级锁&#xff1a;开销小&#xff0c;加锁快&#xff1b;不会出现死锁&#xff1b;锁定粒度大&#xff0c…

正则表达式(常用最新版)

密码 【1】密码必须为包含大小写字母和数字的组合&#xff0c;不能使用特殊字符&#xff0c;长度在6-10之间。 /^(?.*\\d)(?.*[a-z])(?.*[A-Z]).{6,10}$/ 【2】密码必须为包含大小写字母和数字的组合&#xff0c;可以使用特殊字符&#xff0c;长度在6-10之间。 /^(?.*[a-z]…

【快速上手系列】百度富文本编辑器的快速上手和简单使用

【快速上手系列】百度富文本编辑器的快速上手和简单使用 使用步骤 1、首先要把demo下载下来 demo链接&#xff1a; (18条消息) 百度富文本编辑器demo-Javascript文档类资源-CSDN文库 index.html&#xff1a;demo中的测试页面&#xff0c;可以看到很多方法使用 2、新建一个we…

【freeRTOS】操作系统之二-队列

在任何RTOS中&#xff0c;都具有一个重要的通信机制----消息队列。 ​ 队列是任务间通信的主要形式。**它们可用于在任务之间、中断和任务之间发送消息。**在大多数情况下&#xff0c;它们被用作线程安全的FIFO(先进先出)缓冲区&#xff0c;新数据被发送到队列的后面&#xff…

OpenCV图像处理——傅里叶变换

总目录 图像处理总目录←点击这里 十三、傅里叶变换 13.1、原理 我们生活在时间的世界中&#xff0c;早上7:00起来吃早饭&#xff0c;8:00去挤地铁&#xff0c;9:00开始上班。。。 以时间为参照就是时域分析。在频域中一切都是静止的 对傅里叶变换写的很好的一篇文章→ h…

【C++】队列来喽,真的很简单的

我们经历了那么多练习和顺序表&#xff0c;链表&#xff0c;栈的大风大浪&#xff0c;小小一个队列可以说简单至极了 练习&#xff0c;以及顺序表之类的文章都在我的主页哦&#xff0c;请认真学习之后再看本文 目录 1.队列的结构 2.实现 3.栈和队列的相互实现 1.队列的结构 …

Postgresql源码(88)column definition list语义解析流程分析

0 总结 如果调用函数时同时满足以下几种情况 在from后面。返回值为RECORD&#xff08;或者是anyelement表示的RECORD&#xff09;&#xff08;anyelement的实际类型由入参决定&#xff0c;入参是RECORD&#xff0c;返回就是RECORD&#xff09;。返回值被判定为TYPEFUNC_RECOR…

11.18 - 每日一题 - 408

每日一句&#xff1a;不如就利用孤单一人的时间&#xff0c;使自己变得更优秀&#xff0c;给来的人一个惊喜&#xff0c;也给自己一个好的交代 数据结构 1 当一棵有n个结点的二叉树按层次从上到下&#xff0c;同层次从左到右将结点中的数据存放在一维数组A[1…n&#xff3d;中…

Android App事件交互Event之模仿京东App实现下拉刷新功能(附源码 可直接使用)

运行有问题或需要源码请点赞关注收藏后评论区留言~~~ 一、正常下拉与下拉刷新的冲突处理 电商App的首页通常都支持下拉刷新&#xff0c;比如京东首页的头部轮播图一直顶到系统的状态栏&#xff0c;并且页面下拉到顶后&#xff0c;继续下拉会拉出带有下拉刷新字样的布局&#x…

leaflet教程039: 只显示一屏地图,设定范围不让循环延展

第039个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet只显示一屏地图,并且根据maxBounds和bounds的设定,来改变不同的地图呈现状态。 直接复制下面的 vue+leaflet源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共68行)心得总结相…