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

一、树

        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.rhkb.cn/news/382419.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

企业级 线上故障排查思路

线上故障排查思路 WARNING 简介&#xff1a;有哪些常见的线上故障&#xff1f;如何快速定位问题&#xff1f;本文详细总结工作中的经验&#xff0c;从服务器、Java应用、数据库、Redis、网络和业务六个层面分享线上故障排查的思路和技巧。较长&#xff0c;同学们可收藏后再看。…

【HZHY-AI300G智能盒试用连载体验】在华为IoTDA平台上建立设备

目录 华为IoTDA平台 注册IoTDA实例 创建产品 添加设备 本文首发于&#xff1a;【HZHY-AI300G智能盒试用连载体验】 智能工业互联网网关 - 北京合众恒跃科技有限公司 - 电子技术论坛 - 广受欢迎的专业电子论坛! 在上一篇博文中介绍了如何在HZHY-AI300G智能盒创建南向设备&a…

Github Desktop 关于将本地文件夹设置为新仓库的 使用笔记

实际要达到的结果: 将UE5工程同步到Github,工程太大,我们只需要将必要的工程文件夹同步即可,缓存等一些不必要的文件夹则不需要同步 最终效果预览: 1. 将本地文件夹设置为新仓库 将本地文件夹作为仓库一般你是没有这个仓库的,所以你需要新建一个仓库 如果忽略某些不必要的文…

MATLAB绘制方波、锯齿波、三角波、正弦波和余弦波、

一、引言 MATLAB是一种具有很强的数值计算和数据可视化软件&#xff0c;提供了许多内置函数来简化数学运算和图形的快速生成。在MATLAB中&#xff0c;你可以使用多种方法来快速绘制正弦波、方波和三角波。以下是一些基本的示例&#xff0c;展示了如何使用MATLAB的命令来实现正弦…

简单修改,让UE4/5着色器编译速度变快

简单修改&#xff0c;让UE4/5着色器编译速度变快 目录 简单修改&#xff0c;让UE4/5着色器编译速度变快 一、问题描述 二、解决方法 &#xff08;一&#xff09;硬件升级 &#xff08;二&#xff09;调整相关设置和提升优先级 1.调整相关设置 &#xff08;1&#xff09…

昇思25天学习打卡营第17天|计算机视觉

昇思25天学习打卡营第17天 文章目录 昇思25天学习打卡营第17天ShuffleNet图像分类ShuffleNet网络介绍模型架构Pointwise Group ConvolutionChannel ShuffleShuffleNet模块构建ShuffleNet网络 模型训练和评估训练集准备与加载模型训练模型评估模型预测 打卡记录 ShuffleNet图像分…

P6 优化篇 - 数据折线图可视化步骤

增加新页面, 则需要在 page.json里面增加页面信息 2.添加目录, 和路径 同时也要添加目录了 , 新建目录LineChart , 添加文件LineChart.vue 4.LineChart.vue 直接复制黏贴 <template><view class"container"><!-- 图表显示区域 --><view cla…

快速认识EA(Enterprise Architecture)

前言 企业架构&#xff0c;英文是&#xff1a;Enterprise Architecture&#xff0c;简称&#xff1a;EA&#xff0c;是承接企业战略规划与IT建设之间的桥梁&#xff0c;是企业信息化的核心&#xff0c;主要包括业务架构和IT架构。 架构的本质是管理和解决系统的复杂性&#x…

《书生大模型实战营第3期》入门岛 学习笔记与作业:Git 基础知识

文章大纲 Git 是什么&#xff1f;-- 分布式版本控制系统版本控制系统简介Git 基本概念1. 安装 Git1.1 Windows 系统1.2 Linux 系统 2. Git 托管平台3. 常用 Git 操作4. tips4.1 全局设置 vs. 本地设置4.2 如何配置4.3 验证设置4.4 Git 四步曲 5. 常用插件6. 常规开发流程 作业其…

使用MariaDB数据库管理系统

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 一、数据库管理系统 数据库是指按照某些特定结构来存储数据资料的数据仓库&#xff1b; 数据库管理系统是一种能够对数据库中存放的数据进行建立、修…

使用Amazon Web Services Lambda把天气预报推送到微信

最近北京开始下雨&#xff0c;开始和同事打赌几点能够雨停&#xff0c;虽然Iphone已经提供了实时天气&#xff0c;但是还是想用国内的API试试看看是不是更加准确些。 以下是我使用的服务&#xff1a; 地图SDK/APP获取 经纬度彩云天气API 通过地理位置获取天气信息Lambda 作为…

【数学建模】——多领域资源优化中的创新应用-六大经典问题解答

目录 题目1&#xff1a;截取条材 题目 1.1问题描述 1.2 数学模型 1.3 求解 1.4 解答 题目2&#xff1a;商店进货销售计划 题目 2.1 问题描述 2.2 数学模型 2.3 求解 2.4 解答 题目3&#xff1a;货船装载问题 题目 3.1问题重述 3.2 数学模型 3.3 求解 3.4 解…

JavaWeb系列二十三: web 应用常用功能(文件上传下载)

文件上传下载 基本介绍文件上传基本原理文件上传应用实例文件上传注意事项和细节 文件下载基本原理文件下载应用实例文件下载注意事项 ⬅️ 上一篇: JavaWeb系列二十二: 线程数据共享和安全(ThreadLocal) &#x1f389; 欢迎来到 JavaWeb系列二十三: web 应用常用功能(文件上传…

kafka基础介绍

一、为什么使用消息队列 1.使用同步的通信方式来解决多个服务之间的通信 同步的通信方式会存在性能和稳定性的问题。 2.使用异步的通信方式 针对于同步的通信方式来说,异步的方式,可以让上游快速成功,极大提高了系统的吞吐量。而且在分布式系统中,通过下游多个服务的 分布式事…

使用Web控制端和轻量级客户端构建的开放Web应用防火墙(OpenWAF)

目录 1. 简介2. 项目结构3. Web控制端3.1. 功能概述3.2. 审计&#xff08;攻击&#xff09;日志查看3.3. 多个WAF的集中监控和操作3.4. 使用socket进行封装3.5. 日志的高效存储和检索&#xff08;Redis&#xff09; 4. 轻量级客户端4.1. 功能概述4.2. 对Web程序的防护4.3. 网络…

大语言模型-Bert-Bidirectional Encoder Representation from Transformers

一、背景信息&#xff1a; Bert是2018年10月由Google AI研究院提出的一种预训练模型。 主要用于自然语言处理&#xff08;NLP&#xff09;任务&#xff0c;特别是机器阅读理、文本分类、序列标注等任务。 BERT的网络架构使用的是多层Transformer结构&#xff0c;有效的解决了长…

docker文件挂载和宿主主机文件的关系

一、背景 在排查docker日志时发现在读取docker的文件时找不到文件&#xff0c;在宿主主机上可以查到对应的文件。这里就要理解docker文件目录和宿主主机上的文件的关系。 二、Docker文件系统和宿主系统 Docker文件和宿主文件之间的关系主要体现在Docker容器的运行环境中。Docke…

CSS画边框线带有渐变线和流光边框实例

流光边框css流光边框动画效果_哔哩哔哩_bilibili流光边框css流光边框动画效果_哔哩哔哩_bilibili纯CSS写一个动态流水灯边框的效果&#xff5e;_哔哩哔哩_bilibili荧光边框CSS 动画发光渐变边框特效_哔哩哔哩_bilibili [data-v-25d37a3a] .flow-dialog-custom {background-col…

recursion depth exceeded” error

有些时候不可以用jax.jit装饰器 参考资料&#xff1a;使用 JAX 后端在 Keras 3 中训练 GAN |由 Khawaja Abaid |中等 (medium.com)

字符的统计——423、657、551、696、467、535

423. 从英文中重建数字 最初思路 首先要有一个指针&#xff0c;对于3/4/5为一组地跳跃。起初想的是后瞻性&#xff0c;如果符合0-9任意&#xff0c;则更换index、跳跃。此时写了一个函数&#xff0c;用来判断s的截取段和0-9中有无符合。这个思路并没有进行下去&#xff0c;虽然…