【数据结构初阶】6. 树和堆

news2025/2/22 12:24:51

1. 树概念及结构

1.1 树的概念

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

  • 有一个特殊的结点,称为根结点,根节点没有前驱结点
  • 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
  • 因此,树是递归定义的。 在这里插入图片描述

任何一个结点都包含0~N个孩子结点,结点可以分成自身结点和子树(跟自己同样结构定义的树)两部分
例如:A结点可以分成自身A结点,B子树,C子树,D子树。

注意:树形结构中,子树之间不能有交集,否则就不是树形结构
在这里插入图片描述
上图的数据结构类型都不是树,而是图的概念

1.2 树的相关概念

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

其中节点的层次是可以自己定义的,若如上所述:将根定义为第一层,那么树的深度/高度就是4 ,空树的深度为0;(更加符合日常生活中的认知) ,同样也可以将根定义为第0层,那么树的深度/高度就是3,空树的深度为-1;
拓展:为什么数组的下标要从0开始?
因为数组访问的是内存地址空间,例如,访问a[0],其实等价与*(a+0)
a表示数组首元素的地址,在首元素的地址上再加0 正好契合地址的访问

1.3 树的表示

树的表示方法多种多样:

1.3.1 静态数组

在这里插入图片描述
规定孩子结点的上限为N,当面对超过N个孩子结点时无法存储,而叶子结点没有孩子结点时,空间浪费严重,所以通常不采用

1.3.2 顺序表

在这里插入图片描述
通过struct TreeNode* 指向struct TreeNode的孩子结点,再创建一个数组存储这些struct TreeNode* ,所以是二级指针的形式
实现动态存储孩子结点

1.3.3 孩子兄弟表示法

typedef int DataType;
struct TreeNode
{
 struct TreeNode* child; // 左边第一个孩子结点
 struct TreeNode* brother; // 指向其下一个兄弟结点
 DataType data; // 结点中的数据域
};

在这里插入图片描述

在这里插入图片描述
child 指向左边的第一个孩子结点,而brother用于链接下一个兄弟结点
同一层的孩子结点都通过brother链接起来,而访问下一层就访问第一个孩子结点(child)开始
这种方式实现,树的结构可以还原出来,也不会出现环路的情况。

1.3.4 双亲表示法

在这里插入图片描述
通过数组的方式实现,单纯的存储父亲结点的下标,适合结点找祖先的情况

1.4 树在实际中的应用

在这里插入图片描述
通常用来表示操作系统中的文件系统的目录树结构

2. 二叉树概念及结构

2.1 概念

一棵二叉树是结点的一个有限集合,该集合:

  1. 或者为空
  2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成 在这里插入图片描述
    从上图可以看出:
  • 二叉树不存在度大于2的结点
  • 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
    注意:对于任意的二叉树都是由以下几种情况复合而成的:
    在这里插入图片描述

2.2 现实中的二叉树:

在这里插入图片描述
左图为标准的满二叉树,右图为完全二叉树

2.3 特殊的二叉树:

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。
    也就是说,如果一个二叉树的层数为K,且结点总数是2k -1,则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。
    对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。
    要注意的是满二叉树是一种特殊的完全二叉树。
    在这里插入图片描述

3. 堆

gitee代码提交:堆的代码实现

3.1 堆的概念及结构

这里的堆指的也是数据结构中的一种,要和操作系统中的堆(指的是进程地址内存区域的划分)区分开

在这里插入图片描述

3.2 堆的代码实现(重点)

实现堆过程中出现的问题:在这里插入图片描述

01 堆结构体声明

typedef int HPDataType;

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

使用类似顺序表:动态开辟数组的方式实现堆

02 初始化接口

void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->capacity = php->size = 0;
}

03 插入接口(重点)

要实现插入接口首先要保证插入数据后,还得保持堆的数据结构特征:小根堆:双亲<孩子 / 大根堆:双亲>孩子
这里就需要用到向上调整算法
在这里插入图片描述

// 交换两结点的值要传 HPDataType* 类型
// 更标准的写法
//void Swap(HPDataType* p1, HPDataType* p2)
//{
//	HPDataType tmp = *p1;
//	*p1 = *p2;
//	*p2 = tmp;
//}
void Swap(HPDataType* a, int parent, int child)
{
	int tmp = a[child];
	a[child] = a[parent];
	a[parent] = tmp;
}
// 向上调整算法
void AdjustUp(HP* php, int child)
{
	//向上和父节点进行比较
	int parent = (child - 1) / 2;
	//while (parent >= 0) 
	while (child > 0)
	{
		// 小跟堆(要实现大根堆将 > 换成 < 即可)
		if (php->a[parent] > php->a[child])
		{
			// 交换
			Swap(php->a, parent, child);
			// 往上遍历
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	// 扩容
	if (php->capacity == php->size)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(int));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		php->capacity = newCapacity;
		php->a = tmp;
	}
	// 1.插入结点
	php->a[php->size] = x;
	php->size++;
	// 2.调整
	AdjustUp(php, php->size - 1);
}

04 弹出接口(重点)

因为堆主要实现的是Topk(前k大/前k小)问题,所以弹出实现的是将堆顶元素(最小/最大元素)弹出
如果只是单纯的将数组往前覆盖可以实现嘛?
在这里插入图片描述
很明显无法采用,这里就需要采用向下调整算法

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
int array[] = {27,15,19,18,28,34,65,49,25,37};
在这里插入图片描述
若要实现向下调整算法,要保证左右子树都为小根堆 / 大根堆

在这里插入图片描述

void AdjustDown(HP* php, int parent)
{
	// 找最小孩子
	// 假设左孩子为最小孩子
	int minchild = parent * 2 + 1;
	while (minchild < php->size)
	{
		//若 右孩子 < 左孩子
		if (php->a[minchild + 1] < php->a[minchild] && minchild + 1 < php->size)
		{
			// 最小孩子换成右孩子
			minchild = parent * 2 + 2;
		}
		//上述确定最小孩子
		if (php->a[parent] > php->a[minchild])
		{
			Swap(php->a, parent, minchild);
			parent = minchild;
			minchild = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	Swap(php->a, 0, php->size - 1);
	php->size--;
	AdjustDown(php, 0);
}

05 销毁接口

void HeapDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->size = php->capacity;
}

06 判空接口

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

07 返回堆顶元素

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

08 打印接口

void HeapPrint(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	for (int i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

09 堆大小接口

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

10 测试接口

void TestHeap()
{
	// 创建数组
	int a[] = { 65, 100, 70, 32, 50, 60 };
	HP hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
	{
		// 将整个数组插入进去
		HeapPush(&hp, a[i]);
	}
	HeapPrint(&hp);
	HeapPop(&hp);
	HeapPrint(&hp);
}

看打印的结果是否符合堆的数据结构特征
在这里插入图片描述
验证发现,符合!成功实现堆
其实我们发现通过实现堆也可以快速的实现堆排序:
在这里插入图片描述

4. 堆排序

如果在堆创建完毕的情况下,实现堆排序仅仅是几行代码,但是当我们遇到未建堆的数组,不可能通过现场实现堆的方式(代价太大:首先要实现堆数据结构,然后不断调用接口Push插入堆)
那么如何快速建堆呢?
gitee代码提交:堆排序的代码实现

4.1 建堆

在这里插入图片描述
这里采取的向下调整建堆,建小堆后只是保证堆顶的元素为最小值,子树还不是有序的

4.2 建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):
在这里插入图片描述
在这里插入图片描述
所以向上调整和向下调整建堆,相对而言向下调整建堆更优

4.3 升序选大堆还是小堆

在这里插入图片描述
在这里插入图片描述
升序 – 大堆
降序 – 小堆

5. TopK问题

在这里插入图片描述
在这里插入图片描述
建堆选数:时间复杂度为O(N)的算法

TopK问题建小堆选数(文件版)的代码实现

gitee代码提交:Heap_TopK 堆解决TopK问题

01 创建N个随机数的Data文件

void CreateDataFile(const char* filename, int N)
{
	//写入
	FILE* fin = fopen(filename, "w");
	if (fin == NULL)
	{
		perror("fopen fail");
		exit(-1);
	}
	//随机种子
	srand((unsigned int)time(NULL));
	for (int i = 0; i < N; i++)
	{
		//输入N个随机数
		fprintf(fin, "%d\n", rand() % 10000);
		// 所有数在0~10000 内 再手动插入10个大于10000的数 看选数过后是否选的出来
	}
	fclose(fin);
}
int main()
{
	const char* filename = "Data.txt";
	//从N个数当中选出前K大
	int N = 10000;
	int K = 10;
	// 创建N个数的文件
	CreateDataFile(filename, N);
	return 0;
}

在这里插入图片描述
该函数只能调用一次,因为是“w”的形式打开文件,若文件有内容会进行删除重写

02 读取写入Data当中的数据

void PrintTopK(const char* filename, int K)
{
	assert(filename);
	//读取数据
	FILE* fout = fopen(filename, "r");
	if (fout == NULL)
	{
		perror("read fail");
		exit(-1);
	}

以只读的方式打开filename == “Data.txt” 的文件

03 开辟K个空间建堆

	//将前K个数据取出
	int* minHeap = (int*)malloc(sizeof(int) * K);
	if (minHeap == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	
	// 将文件前K个数插入minHeap
	for (int i = 0; i < K; i++)
	{
		fscanf(fout, "%d", &minHeap[i]);
	}

	//将K个数建堆 (K-2)/2 最后一个叶子结点
	for (int j = (K - 2) / 2; j >= 0; j--)
	{
		//向下调整建堆
		AdjustDown(minHeap, K, j);
	}

04 选N-K次数

	//继续读取后N-K
	int val = 0;
	while (fscanf(fout, "%d", &val) != EOF)
	{
		// 选出比当前堆大的元素 放入堆中
		if (val > minHeap[0])
		{
			minHeap[0] = val;
			AdjustDown(minHeap, K, 0);
		}
	}

经过前几步的操作,当前 minHeap 内数据为前K个最大数

05 将堆内数据打印

	for (int i = 0; i < K; i++)
	{
		printf("%d ", minHeap[i]);
	}
	free(minHeap);
	fclose(fout);
}

在这里插入图片描述

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

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

相关文章

什么是元学习

什么是元学习 元学习介绍 元学习希望能够使得模型获取调整超参数能力&#xff0c;使其可以在获取已有的知识基础上&#xff0c;快速学习新任务。 元学习目的 元学习和机器学习的区别在于&#xff1a;机器学习是先人为调参&#xff0c;之后直接训练特定任务下的深度学习模型…

U盘打不开,提示格式化怎么办?不小心确定U盘格式化如何找回数据?

U盘是很常用的数据存储设备&#xff0c;存储空间大&#xff0c;携带方便。 很多用户在使用U盘的过程中&#xff0c;经常会遇到U盘格式化弹窗提示的问题。如果每次插入U盘都出现这个提示就会很影响使用&#xff0c;最糟糕的结果是不小心点了格式化&#xff0c;那么之前储存的文…

osg+shader光照半透明

先记录下&#xff0c;免得时间久了忘了。 对于光照&#xff0c;光源有点光源&#xff0c;聚光灯&#xff0c;方向光。每种光源又有ambient,diffuse,specular,emission几个属性。 这里用点光源&#xff08;不考虑衰减&#xff09; 1&#xff0c;diffuse是入射光与物体法线之间…

R语言raster包计算多个栅格图像平均值、标准差的方法

本文介绍基于R语言中的raster包&#xff0c;批量读取多张栅格图像&#xff0c;对多个栅格图像计算平均值、标准差&#xff0c;并将所得新的栅格结果图像保存的方法。 在文章R语言raster包批量读取单一或大量栅格图像&#xff08;https://blog.csdn.net/zhebushibiaoshifu/artic…

Spring 获取resource下文件路径

两种方式String path "excel/constant.xlsx" &#xff1b;// 直接获取文件1 File file1 new DefaultResourceLoader().getResource(path).getFile(); // 获取路径2 String file Thread.currentThread().getContextClassLoader().getResource(path).getFile(); Inp…

mac xcode工具 配置github 流程

注&#xff1a;这个方法是服务于私有仓库配置&#xff0c;同时建立与之前本机 多端仓库配置的基础上实现&#xff0c;如有疑问&#xff0c;请留言。 这个方法可行&#xff0c;但不一定是最好。 1、初始化一个私有github仓库&#xff0c;初始化github 的token【这里一定记得要…

容器在公有云上的落地姿势

1.容器天生隔离能力不足1.1 容器是一种进程隔离技术&#xff0c;并非虚拟化技术容器&#xff08;container&#xff09;&#xff0c;并不是一种虚拟化&#xff08;virtualization&#xff09;技术&#xff0c;而是一种进程隔离&#xff08;isolation&#xff09;技术&#xff0…

6.6、电子邮件

电子邮件&#xff08;E-mail&#xff09;是因特网上最早流行的一种应用\color{red}最早流行的一种应用最早流行的一种应用&#xff0c;并且仍然是当今因特网上最重要、最实用的应用之一 传统的电话通信属于实时通信&#xff0c;存在以下两个缺点: 电话通信的主叫和被叫双方必须…

Raki的读paper小记:ConTinTin: Continual Learning from Task Instructions

Abstract&Introduction&Related Work 研究任务 Continual Learning from Task Instructions已有方法和相关工作面临挑战创新思路 InstructionSpeak包含两个策略&#xff0c;充分利用task instruction来改善前向转移和后向转移&#xff1a; 一个是从negative的输出中学…

nodejs图片上传/头像上传

项目结构 utils文件夹无用,没文件 前端代码 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"utf-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport&…

大屏展示项目开发常用属性

图例legend前言&#xff1a;1.[配置项手册](https://echarts.apache.org/zh/option.html#title)2.[使用手册可以快速入门](https://echarts.apache.org/handbook/zh/get-started/)3.4.一、图例legend1.1 设置图例的位置 &#xff08;上下左右&#xff09;1.1.1 上下&#xff08…

【爪洼岛冒险记】第2站:带你学会:Java中三种输出语句,java中基本数据类型,学会变量,常量,类型转换知识点

&#x1f331;博主简介&#xff1a;是瑶瑶子啦&#xff0c;一名大一计科生&#xff0c;目前在努力学习C进阶、数据结构、算法、JavaSE。热爱写博客~正在努力成为一个厉害的开发程序媛&#xff01; &#x1f4dc;所属专栏&#xff1a;爪洼岛冒险记 ✈往期博文回顾&#xff1a;【…

【译】eBPF 和服务网格:还不能丢掉 Sidecar

服务网格以典型的 sidecar 模型为人熟知&#xff0c;将 sidecar 容器与应用容器部署在同一个 Pod 中。虽说 sidecar 并非很新的模型&#xff08;操作系统的 systemd、initd、cron 进程&#xff1b;Java 的多线程&#xff09;&#xff0c;但是以这种与业务逻辑分离的方式来提供服…

主库出问题了,从库怎么办?

在前面的文章中,我和你介绍了 MySQL 主备复制的基础结构,但这些都是一主一备的结构。 大多数的互联网应用场景都是读多写少,因此你负责的业务,在发展过程中很可能先会遇到读性能的问题。而在数据库层解决读性能问题,就要涉及到接下来两篇文章要讨论的架构:一主多从。 今…

Qt之标准对话框(QColorDialog、QInputDialog、QFontDialog)

文章目录QColorDialog使用方式QInputDialog使用方式QFontDialog使用方式提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 QColorDialog QDialog类用于指定颜色的。 使用方式 设置最开始的颜色 setCurrentColor(Qt::red);//其他的颜色Qt::white Qt::…

迪文串口屏(1)-DMG80480C070_03WTC

由于成本缘故&#xff0c;用迪文串口屏去替换项目里的大彩串口屏&#xff0c;样品型号为DMG80480C070_03WTC。不过说句实话&#xff0c;迪文串口屏没有大彩串口屏那么容易上手。产品命名及硬件特性10Pin_1.0mm座子&#xff0c;7.0英寸&#xff0c;800*480分辨率&#xff0c;16.…

机器学习--方差和偏差、Bagging、Boosting、Stacking

目录 一、方差和偏差 数学定义 对公式的解释 减小偏差、方差、噪声 总结 二、Bagging 代码实现 bagging什么时候会变好 不稳定的learner ​总结 三、Boosting Gradient boosting gradient boosting 的代码实现 gradient boosting的效果 ​总结 四、Stacking st…

秒杀项目总结

秒杀就是同一个时刻有大量的请求争抢购买同一个商品&#xff0c;并且完成交易的过程 也就是大量的并发读和并发写 先制作一个增删改查的秒杀系统&#xff0c;但是想让这个系统支持高并发访问就没那么容易了&#xff0c; 如何让这个秒杀系统面对百万级的请求流量不出故障&…

02-final、finally、finalize的区别?

1.final final是java中的修饰符&#xff0c;用于修饰属性&#xff08;变量&#xff09;、方法、类。 1.被final修饰的变量不可以被改变,如果修饰引用,那么表示引用不可变,引用指向的内容可变. 被修饰的常量,在编译阶段会存入常量池中. 2.被final修饰的方法不可以被重写, 被修…

图片播放器的实现1——利用Image2LCD提取图片数据并显示

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 参考内容 &#xff08;1&#xff09;https://xiefor100.blog.csdn.net/article/details/71941527 &#xff08;2&#xff09;https://xiefor100.blog.csdn.net/article/details/78529519 内容总结 …