【数据结构】二叉树——顺序结构——堆及其实现

news2024/11/15 4:36:02

一、树

        1.1、树的概念和结构

树是一种非线性的数据结构,它是由n(n>=0)个有限节点组成的一个具有层次关系的集合。

        树有一个特殊的节点,称为根节点,根节点没有前驱结点。

        除根节点外,其余部分被分为M(M>0)个互不相交的集合T1、T2、.....Tm,其中的每一个集合 Ti(1 <= i <= m)又是一颗结构与数类似的子树。每一颗子树的根节点都有且只有一个前驱节点,而可以有0个或者多个后继节点。

        树是递归定义的。

在树形结构中,子树之间不能存在交集

        

子树中存在交集,就不是树形结构了

除了根节点以外,每一个节点有且只有一个父节点

如图,E节点存在两个父节点,该图不是树形结构。

一颗N个节点的树有 N-1条边

        1.2、树的相关术语

在树形结构中,有一些相关术语:

        父节点(双亲节点):如果一个节点含有子节点,则这个节点称为其子节点的父节点;如上图:A就是B的父节点。

        子节点:一个节点含有的子树的根节点称为该节点的子节点;如上图:B是A的子节点。

        节点的度:一个节点存在几个子节点,它的度就是多少;如上图:A的度为6、F的度为2、K的度为0.

        树的度:在一个树形结构中,最大的节点的度,称为树的度;如上图:树的度为6。

        叶子结点(终端节点):度为0的节点就是叶子节点;简单来说,就是没有子节点(下一个节点);如上图:B、C、H、I 等节点都是叶子结点。

        分支节点:度不为0的节点称为分支节点;如上图:D、E、F、G 等节点都是分支节点。

        兄弟节点:具有相同的父节点的节点称为兄弟节点;如上图:B和C是 兄弟节点。

        节点的层次:从树形结构的跟开始定义,跟为第 1 层,跟的子节点为第 2 层,以此类推。

        树的高度:树中节点的最大层次;如上图:树的高度是 4 。

        节点的祖先:从根到该节点所经过的分支上的所以节点。如上图:A是所有节点的祖先。

        路径:一条从树的任意节点出发,沿父节点-子节点连接,到达任意节点的序列。比如上图中A到Q的路径:A-E-J-Q。

        子孙:以某节点为根的子树中任一节点都称为该节点的子孙。

        森林:右m(m>0)个互不相交的树组成的集合称为森林。

        1.3、树的表示

        树的表示相对于线性表就复杂了,想要存储表示起来就很麻烦了,这里既要保存值域,也要保存节点和节点之间的关系;树有很多中表示方法,就比如:双亲表示法,孩子表示法,孩子双亲表示法以及孩子兄弟表示法等

        这里看一下简单的孩子兄弟表示法

struct TreeNode
{
    struct Node* child; // 左边开始的第⼀个孩⼦结点
    struct Node* brother; // 指向其右边的下⼀个兄弟结点
    int data; // 结点中的数据域
};

           

    这样的一个树就可以表示成下面这种形式

        1.4、树形结构的分类和应用

树形结构分为很多种,具体如上图,

        树形结构实际应用:

        最典型的就是,计算机存储和管理文件的文件系统。它利用树形结构来组织和管理文件和文件夹。再文件系统中,树结构被广泛利用。通过父节点和子节点之间的关系来表示不同层级的文件和文件夹之间的关系

二、二叉树

        2.1、二叉树的概念与结构

        二叉树是树形结构的一种。

        根据上图,我们不难看出二叉树具备以下特点:

  • 二叉树不存在度大于 2  的节点 
  • 二叉树的子树有左右之分,次序不能颠倒,因此,二叉树是有序树。

        2.2、特殊的二叉树

2.2.1、满二叉树

        一个二叉树,如果每一个层的节点数都达到最大值,则这个二叉树就是满二叉树。也就是,如果二叉树的层数为k,且节点总数为2^k - 1,则它就是满二叉树。

2.2.2、完全二叉树

        完全二叉树是效率很高的 数据结构,完全二叉树是由满二叉树而引出来的。对于深度为k的,有n个节点的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中编号从1到n的节点一一对应时称之为完全 二叉树。要注意的是,满二叉树是一种特殊的二叉树。

2.2.3、二叉树的存储结构

        二叉树,我们可以使用两种结构存储;一种是顺序结构,另外一种就是链式结构。

顺序结构

        顺序结构,就是使用数组来存储;而数组一般只适合表示完全二叉树(如果用数组表示不完全二叉树,就会导致空间的浪费)。所以对于完全二叉树来说,就更适合使用顺序结构存储。

        我们通常使用堆(一种二叉树(数据结构))顺序结构的数组来存储二叉树顺序结构(完全二叉树)

链式结构

        对于链式结构,本篇不做详解;在下一篇文章会详细讲解二叉树的链式结构。

三、二叉树顺序结构——堆及其实现

        3.1、堆的概念

        堆是一种满足特定条件的完全二叉树,主要存在以下两种结构

小顶堆:任意节点的值 <= 其子节点的值

大顶堆:任意节点的值 >= 其子节点的值

根据图,我们可以看出堆具有一些特性:

  • 最底层的节点靠左,其余层的节点都被填满

  • 二叉树的根节点称为“堆顶”,底部最右边的节点称为“根节点”
  • 大堆的堆顶元素的值是最大的;小堆的堆顶元素的值是最小的

这里补充:

        对于堆这样的数据结构存储二叉树:

对于具有 n 个结点的完全⼆叉树,如果按照从上⾄下从左⾄右的数组顺序对所有结点从
0 开始编号,则对于序号为 i 的结点有:
  1. 若 i>0 , i 位置结点的双亲序号: (i-1)/2 ; i = 0 , i 为根结点编号,无父节点
  2. 若 2i+1<n ,左孩子序号: 2i+1 , 2i+1>=n 否则无左孩子节点
  3. 若 2i+2<n ,右孩子序号: 2i+2 , 2i+2>=n 否则无有孩子节点

        3.2、堆的实现

        现在来实现这样一个堆结构。

堆的底层结构是一个数组,定义堆的结构

3.2.1、堆的结构

//堆的结构
typedef int HPDataType;
typedef struct HeapNode
{
	HPDataType* arr;
	int size; //有效数据个数
	int num;  //空间大小
}HP;

3.2.2、初始化和销毁

        这里堆的底层结构是数组,初始化和销毁与之前顺序表基本一样。

//初始化
void HPInit(HP* php)
{
	assert(php);
	php->arr = NULL;
	php->size = php->num = 0;
}
//销毁
void HPDesTroy(HP* php)
{
	assert(php);
	if (php->arr)
		free(php->arr);
	php->arr = NULL;
	php->size = php->num = 0;
}

3.3.3、判断堆是否为空

        判断堆是否为空,直接判断堆中数据个数是否为0即可。

//判断是否为空
bool HPEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

3.3.4、求堆中数据的个数

        因为堆结构中的size成员就是堆中数据的个数,直接返回。

//求数据个数size
int HPSize(HP* php)
{
	assert(php);
	return php->size;
}

3.3.5、插入数据

        这里向堆中插入数据,我们还要保证堆的结构不被破坏,就只好将数据插入堆之后,再调整数据(这里因为是在堆底(也就是数组的最后)插入的数据,我们就使用向上调整算法)

void Swap(HPDataType* x, HPDataType* y)
{
	HPDataType tmp = *x;
	*x = *y;
	*y = tmp;
}
//向上调整算法
void AdjustUp(HPDataType* arr, int child)
{
	assert(arr);
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//小堆
		if (arr[parent] > arr[child])
		{
			Swap(&arr[parent], &arr[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
//插入数据
void HPPush(HP* php, HPDataType x)
{
	assert(php);
	//判断空间够不够
	if (php->num <= php->num)
	{
		int newnum = (php->num == 0) ? 4 : 2 * php->num;
		HPDataType* tmp = (HPDataType*)realloc(php->arr, newnum * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc filed");
			exit(1);
		}
		php->arr = tmp;
		php->num = newnum;
	}
	//空间足够,插入数据
	php->arr[php->size++] = x;
	//调整堆结构
	AdjustUp(php->arr, php->size - 1);
}

3.3.6、堆的向上调整算法

        堆的向上调整算法,这里以小堆为例

将新的数据插入到数组的尾上,我们用向上调整,直到满足堆的结构

        将元素插入到堆的末尾之后,再进行向上调整
        出入数据之后,如果不满足堆的结构(小堆就是,子节点大于父节点了),就将插入节点顺着父节点向上进行调整即可。

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

3.3.7、删除数据

        删除数据,这里删除的是堆的堆顶数据,数据在删除后,堆结构可能被破坏,这里也需要进行调整,使用的是向下调整算法。

删除数据,先将堆顶数据和堆底数据进行调换,再从堆顶开始往下调整

//删除数据
void HPPop(HP* php)
{
	assert(php);
	Swap(&php->arr[0], &php->arr[php->size - 1]);
	php->size--;
	AdjustDown(php->arr, 0, php->size);
}

3.3.8、堆的向下调整算法

        向下调整算法,以小堆为例

如果该节点的值大于子节点,就向下调整,直到结束(结束条件,遍历完数组或者堆里的数据满足堆结构了)

void Swap(HPDataType* x, HPDataType* y)
{
	HPDataType tmp = *x;
	*x = *y;
	*y = tmp;
}
//向下调整算法
void AdjustDown(HPDataType* arr, int parent, int n)
{
	assert(arr);
	int child = parent * 2 + 1;
	while (child < n)
	{
		//找到两个子节点中小的节点
		if (child<n - 1 && arr[child]>arr[child + 1])
		{
			child++;
		}
		if (arr[child] < arr[parent])
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

到这里,堆的基本操作就实现完了。

        3.3、堆的实际应用

3.3.1、堆排序

堆的一个应用就是堆排序,这里简单介绍一下,后面会有详细理解

        对于堆排序,我们可以基于本篇实现的堆结构来实现堆排序

如果要排升序,就建大堆;排降序,就排小堆。

基于堆结构来进行堆排序

void HeapSort(int* a, int n)
{
	// a数组直接建堆 
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

        这样来实现,时间复杂度为O(n*log n)。

当然,我们也可以脱离这样的堆结构,利用堆这种思想来实现堆排序,这个在后面有详细介绍。

3.3.2、TOP-K问题

        TOP-K 问题:求数据集合中前K个最大的元素或者最小的元素,(一般这样的数据量特别的大)。

而对于这种问题,能想到的最简单最直接的方法就是排序,而数据量如果很大很大,排序不可取了(数据不能一下子全部加载到内存中)。而堆就可以来解决这样的问题。

思路:

1> 取数据集合的前k个元素来建堆

        如果需要前k个最大的元素,就建小堆

        如果需要前k个最小的元素,就建大堆

2> 用剩余的数据依次和堆顶元素进行比较,如果不满足条件,就替换堆顶元素

        将剩余的元素依次和堆顶元素比较完之后,堆中剩余的k个元素就是所求的前k个最小或者最大的元素

这里简单实现一下这样的TOP-K问题

void CreateNDate()
{
	// 造数据
	int n = 100000;
	srand(time(0));
	const char* file = "data.txt";
	FILE * fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}
	for (int i = 0; i < n; ++i)
	{
		int x = (rand() + i) % 1000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}
void TOPk()
{
	int k = 0;
	printf("请输入k:");
	scanf("%d", &k);

	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen fail!");
		exit(1);
	}
	int* minHeap = (int*)malloc(k * sizeof(int));
	if (minHeap == NULL)
	{
		perror("malloc fail!");
		exit(2);
	}

	//从文件中读取前K个数据
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minHeap[i]);
	}

	//建堆---小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(minHeap, i, k);
	}

	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)
	{
		//读取到的数据跟堆顶的数据进行比较
		//比堆顶值大,交换入堆
		if (x > minHeap[0])
		{
			minHeap[0] = x;
			AdjustDown(minHeap, 0, k);
		}
	}

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

	fclose(fout);
}

假设现在我们要从这十万个数据里取5个最大的数据

        先随机生成十万个数据存储到文件data.txt中,我们再设置一下这5个最大的数据

现在这5个数据是最大的(其余的数据都小于100000)

运行看一下结果这里正是这5个数据。

感谢各位大佬支持并指出问题,

                        如果本篇内容对你有帮助,可以一键三连支持以下,感谢支持!!!

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

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

相关文章

Mysql - 索引

目录 一、存储引擎 二、索引 索引结构 索引分类 索引语法 联合索引 前缀索引 索引使用规则 最左前缀法则 范围查询使索引失效 字段做运算操作索引失效 字符串字段不加单引号索引失效 字段做前模糊查询索引失效 or连接条件索引失效 数据发布情况索引失效 指定使用…

Matlab编程资源库(1)选择结构

一、if语句 在 MATLAB 中&#xff0c; if 语句有 3 种格式。 (1) 单分支 if 语句&#xff1a; if 条件 语句组 end 当条件成立时&#xff0c;则执行语句组&#xff0c;执行完之后&#xff0c; 继续执行 if 语句的后继语句&#xff0c;若条件不成 立&#xff0c;则直接执…

什么是PCB流锡槽焊盘/C型焊盘,如何设计?-捷配笔记

在PCB进行机器组装器件时&#xff08;如波峰焊&#xff09;&#xff0c;为了防止部分需要二次焊接的元器件的焊盘堵孔&#xff0c;就需要在PCB焊盘上面开个过锡槽&#xff0c;以便过波峰焊时&#xff0c;这些焊锡会流掉。开流锡槽就是在焊盘裸铜&#xff08;敷锡&#xff09;部…

setsockopt选项对tcp速度

GPT-4 (OpenAI) 每个setsockopt调用都涉及到一个套接字描述符&#xff0c;一个指定网络层的常数&#xff08;如IPPROTO_IP, IPPROTO_TCP, IPPROTO_IPV6, SOL_SOCKET等&#xff09;&#xff0c;一个指定需配置的选项的常数&#xff0c;一个指向配置值的指针&#xff0c;以及那个…

04-数据库MySQL

一、项目要求 二、项目过程介绍 1、新建数据库 2、新建表 3、处理表 1.修改student 表中年龄(sage)字段属性&#xff0c;数据类型由int 改变为smallint 2.为Course表中Cno 课程号字段设置索引,并查看索引 3.为SC表建立按学号(sno)和课程号(cno)组合的升序的主键索引&#xf…

20分钟上手新版Skywalking 9.x APM监控系统

Skywalking https://skywalking.apache.org/ Skywalking是专为微服务、云原生和基于容器的&#xff08;Kubernetes&#xff09;架构设计的分布式系统性能监控工具。 Skywalking关键特性 ● 分布式跟踪 ○ 端到端分布式跟踪。服务拓扑分析、以服务为中心的可观察性和API仪表板。…

英迈中国与 Splashtop 正式达成战略合作协议

2024年7月23日&#xff0c;英迈中国与 Splashtop 正式达成战略合作协议&#xff0c;英迈中国正式成为其在中国区的战略合作伙伴。此次合作将结合 Splashtop 先进的远程桌面控制技术和英迈在技术服务与供应链管理领域的专业优势&#xff0c;为中国地区的用户带来更加安全的远程访…

Android TabLayout的简单用法

TabLayout 注意这里添加tab&#xff0c;使用binding.tabLayout.newTab()进行创建 private fun initTabs() {val tab binding.tabLayout.newTab()tab.text "模板库"binding.tabLayout.addTab(tab)binding.tabLayout.addOnTabSelectedListener(object : TabLayout.On…

【开源库编译 | zlib】 zlib库最新版本(zlib-1.3.1)在Ubuntu(Linux)系统下的 编译 、交叉编译(移植)

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

VSCode | 修改编辑器注释的颜色

1 打开VsCode的设置进入settings.json 2 添加如下代码 "editor.tokenColorCustomizations": {"comments": "#17e917"},3 保存即可生效

第十四届蓝桥杯省赛C++C组C题【三国游戏】题解(AC)

解题思路 由于三种国家都有获胜的可能&#xff0c;所以我们需要分别枚举 X , Y , Z X,Y,Z X,Y,Z 获胜的情况。 设 X X X 获胜&#xff0c;那么对于第 i i i 个事件的贡献为 a [ i ] − ( b [ i ] c [ i ] ) a[i]-(b[i]c[i]) a[i]−(b[i]c[i])&#xff0c;根据贪心的策略…

黑马JavaWeb企业级开发(知识清单)03——HTML实现正文:排版(音视频、换行、段落)、布局标签(div、span)、盒子模型

文章目录 前言一、正文排版1. 视频标签: < video >2. 音频标签: < audio >3. 换行标签: < br >4. 段落标签 < p >5. vscode实现 二、布局1. 盒子模型2. 布局标签< div >和< span >3. VScode实现 三、源代码和运行结果总结 前言 本篇文章是…

鸿蒙HarmonyOS【应用开发一、鸿蒙简介】

✍️作者简介&#xff1a;小北编程&#xff08;专注于HarmonyOS、Android、Java、Web、TCP/IP等技术方向&#xff09; &#x1f433;博客主页&#xff1a; 开源中国、稀土掘金、51cto博客、博客园、知乎、简书、慕课网、CSDN &#x1f514;如果文章对您有一定的帮助请&#x1f…

CSS实现的扫光效果组件

theme: lilsnake 图片和内容如有侵权&#xff0c;及时与我联系~ 详细内容与注释&#xff1a; CSS实现的扫光效果组件 代码 技术栈与框架 Vue3 CSS 扫光效果的原理 扫光效果的原理就是从左到右无限循环的一个位移动画 实现方式 适配文字扫光效果的css .shark-box { …

STL常用算法——常用查找算法

自定义类型都要用仿函数判断 1.find() class Person { public:Person(string name,int age){this->m_Name name;this->m_Age age;}bool operator(const Person &p)//重载operator{if (this->m_Name p.m_Name && this->m_Age p.m_Age){return true;…

CSDN的动态在哪里

模板 文章目录 模板 如果对你有帮助&#xff0c;就点赞收藏把&#xff01;(&#xff61;&#xff65;ω&#xff65;&#xff61;)&#xff89;♡ 早上发了一个动态&#xff0c;这是我第一次发动态&#xff0c;然后却发现我不知道动态在哪打开 我找了好几个地方&#xff0c;然后…

FPGA实验6: 有时钟使能两位十进制计数器的设计

一、实验目的与要求 1.. 熟练掌握使用原理图设计较复杂电路&#xff1b; 2. 学习原理图设计中总线的表示以及使用方法。 二、实验原理 运用Quartus II 集成环境下的图形设计方法设计有时钟使能的两位十进制计数器。进行波形仿真和分析、引脚分配并下载到实验设备上进行功能…

JS进阶之以题悟道(一)列表内容排序再渲染

注释很详细&#xff0c;直接上代码 涉及知识点&#xff1a; 正则表达式outerHTMLjoinmap 题干&#xff1a; 我的答案 <!DOCTYPE html> <html><head><meta charset"utf-8" /><meta name"viewport" content"widthdevice-wid…

py3.7.4离线安装openpyxl等错误,无法安装openpyxl...

&#x1f3c6;本文收录于《CSDN问答解答》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

网络安全等级保护:什么是网络安全等级保护?(非常详细)零基础入门到精通,收藏这一篇就够了

关键词&#xff1a; 网络安全等级保护 等级保护 网络 信息系统 旧话重提&#xff0c;一直以来&#xff0c;我们不断强调“等级保护”制度是我国的网络安全领域的基本制度、基本策略和基本方法&#xff0c;是促进信息化健康发展&#xff0c;维护国家安全、社会秩序和公共利益的…