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

一、树

        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.xdnf.cn/news/1486853.html

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

相关文章

《0基础》学习Python——第十九讲__爬虫/<2>

一、用get请求爬取一般网页 首先由上节课我们可以找到URL、请求方式、User-Agent以及content-type 即&#xff1a;在所在浏览器页面按下F12键&#xff0c;之后点击网路-刷新&#xff0c;找到第一条双击打开标头即可查看上述所有内容&#xff0c;将上述URL、User-Agent所对应的…

【故障排除】Unity在编辑器模式下Play时闪退

一开始以为是偶然的情况&#xff0c;但逐渐发现了规律&#xff1a; 每次某个角色释放技能的时候就会闪退。 为了找到问题代码&#xff0c;找了一下存放运行Log的文件夹&#xff1a; 打开 Console 窗口&#xff08;菜单&#xff1a;Window > General > Console&#xff…

记事本案例组件版本(源码分享)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 非常期待和您一起在这个小…

axios请求大全

本文讲解axios封装方式以及针对各种后台接口的请求方式 axios的介绍和基础配置可以看这个文档: 起步 | Axios中文文档 | Axios中文网 axios的封装 axios封装的重点有三个&#xff0c;一是设置全局config,比如请求的基础路径&#xff0c;超时时间等&#xff0c;第二点是在每次…

蓝桥杯单片机学习总结(Day15 超声波测距)

开启超声波模块测距方法&#xff1a; X20106A是一款红外线检波接收的专用芯片&#xff0c;常用于电视机红外遥控接收器。当CX20106A接收到40KHz的信号时&#xff08;第五脚200K的电阻决定了其频率为40KHz&#xff09;&#xff0c;会在OUT脚输出一个低电平下降脉冲。这个信号甚至…

Ubuntu-文件管理器中鼠标右键添加文本文件

文件管理器中鼠标右键添加文本文件 一、概述二、步骤 一、概述 Ubuntu在文管右键发现没有创建文本文件的菜单&#xff0c; 期望如下所示&#xff0c;这样的操作非常简单 二、步骤 找到模板文件夹 在模板文件夹&#xff0c;创建自己想要的文件就好啦 这个也是支持放文件夹去…

PyTorch的模型定义方法

文章目录 1、简介2、导包3、设置属性4、构建数据集5、训练函数5.1、初始准备5.2、训练过程5.3、绘制图像 6、运行效果7、完整代码 &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家博主&#xff0c;专注于Java领域学习&#xff0c;擅长we…

【图形图像-1】SDF

在图形图像处理中&#xff0c;SDF&#xff08;Signed Distance Field&#xff0c;带符号的距离场&#xff09;是一种表示图形轮廓和空间距离的数学结构。它通常用于计算机图形学、文本渲染、碰撞检测和物理模拟等领域。 SDF&#xff08;Signed Distance Field&#xff0c;带符号…

【数据结构】排序算法——Lesson2

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

算法力扣刷题记录 五十七【236. 二叉树的最近公共祖先】和【235. 二叉搜索树的最近公共祖先】

前言 公共祖先解决。二叉树和二叉搜索树条件下的最近公共祖先。 二叉树篇继续。 一、【236. 二叉树的最近公共祖先】题目阅读 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff…

Windows 磁盘分区样式有几种?如何查看电脑分区样式?

在使用 Windows 操作系统的过程中&#xff0c;磁盘分区是一个重要的概念。磁盘分区的方式直接影响到数据存储和系统运行的效率。磁盘分区的时候也有不同的样式&#xff0c;你知道分区类型有哪些吗&#xff1f;不同的分区样式决定了硬盘的分区方式、可支持的最大存储容量以及兼容…

学习笔记:MySQL数据库操作3

1. 创建数据库和表 创建数据库 mydb11_stu 并使用该数据库。创建 student 表&#xff0c;包含字段&#xff1a;学号&#xff08;主键&#xff0c;唯一&#xff09;&#xff0c;姓名&#xff0c;性别&#xff0c;出生年份&#xff0c;系别&#xff0c;地址。创建 score 表&…

Etsy:以手工制品和复古商品闻名的美国淘宝允许AI艺术品售卖

Etsy是一个美国网络商店平台&#xff0c;以手工艺成品买卖为主要特色&#xff0c;曾被纽约时报拿来和eBay&#xff0c;Amazon比较&#xff0c;被誉为“祖母的地下室收藏”。 Etsy 是一家以手工制品和复古商品闻名的美国网络商店平台在线市场&#xff0c;以手工艺成品买卖为主要…

由“微软蓝屏”事件引发的对网络安全与系统稳定性的思考

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、软件更新流程的漏洞与改进二、强化应急响应机制三、技术创新与应用四、关键行业的特殊应对五、用户意识的提升与数据备份六、全球合作与统一标准总结 前言 …

浅谈断言之XML断言

浅谈断言之XML断言 XML断言是JMeter的一个组件&#xff0c;用于验证请求的响应数据是否符合XML结构。这对于测试返回XML格式数据的Web服务特别有用。 如何添加XML断言&#xff1f; 要在JMeter测试计划中添加XML断言&#xff0c;遵循以下步骤&#xff1a; 打开测试计划&…

The Sandbox:虚拟游戏世界生态系统详解

元宇宙由区块链、软件基础、移动应用、控制台等组成&#xff0c;是一个虚拟空间&#xff0c;结合了增强现实&#xff08;AR&#xff09;、虚拟现实&#xff08;VR&#xff09;和在线游戏等元素。它强调互操作性&#xff0c;允许用户在不同的虚拟平台之间自由切换。与传统的现实…

病理AI领域的常用开源工具汇总

小罗碎碎念 本期推文主题&#xff1a;病理AI领域的常用开源工具汇总 我们有快一周的时间没见啦&#xff0c;所以&#xff0c;这一期推文带来一些比较有实用价值的资源。 我总结了5个病理AI领域常用的软件&#xff0c;用专用于注释的&#xff0c;也有包含整个处理流程的&#x…

【Linux】UDP 协议

目录 1. UDP 协议2. UDP 协议的特点:3. UDP 协议的格式4. UDP 的缓冲区基于UDP的应用层协议 1. UDP 协议 UDP (User Datagram Protocol) 是一种面向数据报的传输层协议, 是传输层的重要协议之一; UDP协议提供了一种无连接, 不可靠的数据传输服务; 适用于要求源主机以恒定速率…

主控制类,项目小结,实时更新UI

1.用户的信息进行更改&#xff0c;上传请求&#xff0c;服务端进行直接操作数据库&#xff0c;返回请求&#xff0c;客户端根据返回的请求&#xff0c;进行更新界面。 按照我前一篇所说的&#xff0c;写好了主控制类&#xff0c;和第二线程接受服务端的信息&#xff0c;这时候…

【Hot100】LeetCode—416. 分割等和子集

目录 题目1- 思路2- 实现⭐152. 乘积最大子数组——题解思路 3- ACM 实现 题目 原题连接&#xff1a;416. 分割等和子集 1- 思路 理解为背包问题 思路&#xff1a; 能否将均分的子集理解为一个背包&#xff0c;比如对于 [1,5,11,5]&#xff0c;判断能否凑齐背包为 11 的容量…