【数据结构初阶】链式二叉树接口实现超详解

文章目录

  • 1. 节点定义
  • 2. 前中后序遍历
    • 2. 1 遍历规则
    • 2. 2 遍历实现
    • 2. 3 结点个数
      • 2. 3. 1 二叉树节点个数
      • 2. 3. 2 二叉树叶子节点个数
      • 2. 3. 3 二叉树第k层节点个数
    • 2. 4 二叉树查找值为x的节点
    • 2. 5 二叉树层序遍历
    • 2. 6 判断二叉树是否是完全二叉树
  • 3. 二叉树性质


1. 节点定义

用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址,其基本结构如下:

typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;				//数据struct BinaryTreeNode* left;	//左孩子struct BinaryTreeNode* right;	//右孩子
}BTNode;

链式二叉树的创建方式比较复杂,为了更好地对接口进行调试,我们先手动创建一棵链式二叉树进行测试:

//创建节点
BTNode* BuyBTNode(int val)
{BTNode * newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = val;newnode->left = NULL;newnode->right = NULL;return newnode;
}
BTNode * CreateTree()
{BTNode * n1 = BuyBTNode(1);BTNode * n2 = BuyBTNode(2);BTNode * n3 = BuyBTNode(3);BTNode * n4 = BuyBTNode(4);BTNode * n5 = BuyBTNode(5);BTNode * n6 = BuyBTNode(6);BTNode * n7 = BuyBTNode(7);//手动将他们连接起来成为一棵二叉树n1->left = n2;n1->right = n4;n2->left = n3;n4->left = n5;n4->right = n6;n5->left = n7;return n1;
}

接下来就可以用这棵二叉树对接口进行测试。

2. 前中后序遍历

二叉树不同于之前的顺序结构,不能直接进行依次遍历,必须按照一定的规则进行遍历
注:堆也是一种二叉树,但是堆不能进行遍历,所以没有这方面的考虑。
另外二叉树的链式结构是一个递归的结构,几乎所有的接口实现都需要用到递归的思想。
1

2. 1 遍历规则

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  1. 前序遍历(Preorder Traversal,亦称先序遍历):访问根结点的操作发生在遍历其左右子树之前
    访问顺序为:根结点、左子树、右子树
  2. 中序遍历(lnorder Traversal):访问根结点的操作发生在遍历其左右子树中间
    访问顺序为:左子树、根结点、右子树
  3. 后序遍历(Postorder Traversal):访问根结点的操作发生在遍历其左右子树之后
    访问顺序为:左子树、右子树、根结点

我们以前序遍历解释一下怎么遍历:
2
从根节点1开始遍历,先输出根节点数据1,然后来到左孩子2,输出2,来到2的左孩子3,输出3,来到3的左孩子NULL,回退至3,来到3的右孩子NULL,再回退,3节点遍历完毕,回退至2,来到2的右节点NULL,回退至2,2节点遍历完毕,回退至1,来到1的右孩子4,先输出4,再来到4的左孩子4,输出4,遍历左右俩孩子都为空,回退至上面的4,来到4的右孩子5,输出5后,俩孩子都为空,再最终回退至1,根节点遍历完成,该二叉树遍历完成。
前序遍历结果:123456
中序遍历结果:321546
后序遍历结果:315641

2. 2 遍历实现

在实现时,我们可以把每一个孩子都看成一个二叉树的根节点,以方便递归遍历。

//前序遍历
void BinaryTreePrevOrder(BTNode* root)
{if (!root)return;printf("%d ", root->data);BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);
}
//后序遍历
void BinaryTreePostOrder(BTNode* root)
{if (!root)return;BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);printf("%d ", root->data);
}
//中序遍历
void BinaryTreeInOrder(BTNode* root)
{if (!root)return;BinaryTreePrevOrder(root->left);printf("%d ", root->data);BinaryTreePrevOrder(root->right);
}

2

2. 3 结点个数

2. 3. 1 二叉树节点个数

int BinaryTreeSize(BTNode* root);

二叉树中只有结点,没有把数据个数存储起来,那么我们要怎么获取二叉树中元素的个数呢?
有两种思路,一个是创建全局变量,然后通过任意一种遍历方式遍历二叉树,每遍历一次都让这个变量++,那么就能得到节点的个数了,但是这样会带来两个问题:

  1. 全局变量每次都要归0,但是由于是递归进行遍历的,所以需要写一个子函数用于递归
  2. 在函数内部调用全部变量可能会存在危险,因为这个变量是任何函数都能访问并修改的

基于这样的原因,我们不采用这个方式。
而是通过计算这个节点本身+左子树节点的个数+右子树节点的个数的方式进行递归。

int BinaryTreeSize(BTNode* root)
{if (!root)return 0;return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);	//1就是该节点本身,再加上左右两棵子树
}

2. 3. 2 二叉树叶子节点个数

int BinaryTreeLeafSize(BTNode* root);

叶子节点的特点就是root->left == NULL && root->right == NULL,所以只要加上一个判断是否要+1,其他的就和计算节点个数的思路是一样的。

int BinaryTreeLeafSize(BTNode* root)
{//如果是叶子节点,就没必要继续向下传递了,直接回归就可以了if (!root)return 0;if (root->left == NULL && root->right == NULL)return 1;return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

2. 3. 3 二叉树第k层节点个数

int BinaryTreeLevelKSize(BTNode* root, int k);

在传递时增加一个参数k,来判断递归到哪一层了,如果是目标层数,就+1回归,其他与计算二叉树叶子节点个数思路一致。

int BinaryTreeLevelKSize(BTNode* root, int k)
{//同理,到目标层数之后就不需要继续向下传递了if (!root)return 0;if (k == 1)return 1;return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

2. 4 二叉树查找值为x的节点

BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

在二叉树中遍历并对比数据,如果找到了就返回这个节点的指针,没找到就返回NULL

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (!root)return NULL;if (root->data == x)return root;//需要分别判断左右子树中有没有这个数据//左BTNode* leftfind = BinaryTreeFind(root->left,x);if (leftfind)return leftfind;//右BTNode* rightfind = BinaryTreeFind(root->right,x);if (rightfind)return rightfind;//没找到,返回空return NULL;
}

2. 5 二叉树层序遍历

除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历
设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
实现层序遍历需要额外借助队列这一数据结构。
1
层序遍历就是一层一层地从左到右地进行遍历,比如上面这棵二叉树的层序遍历就是:1 2 3 4 5 6 7

大致思路为:创建一个数据类型为BTNode*的队列,先将根节点入队列,然后分别将它的左右孩子入队列,接着出队列一次。然后把队头的左右孩子(如果不为NULL)入队列,再出队列一次,每次出队列前都要把它的值打印出来,循环以上过程直到队列为空。

注:这里的队列不再实现,可以看这篇博客。

void BinaryTreeLevelOrder(BTNode* root)
{//二叉树判空if (!root)printf("null\n");//创建队列并初始化Queue qu;QueueInit(&qu);//把根节点入队列QueuePush(&qu,  root);while (!QueueEmpty(&qu)){//将队头的左右子树入队列,并将队头数据打印,再出队列一次BTNode* tmp = QueueFront(&qu);QueuePop(&qu);printf("%d ", tmp->data);if (tmp->left)QueuePush(&qu, tmp->left);if (tmp->right)QueuePush(&qu, tmp->right);}//销毁队列QueueDestroy(&qu);printf("\n");
}

2. 6 判断二叉树是否是完全二叉树

bool BinaryTreeComplete(BTNode* root);

完全二叉树的特点是第X层没有完全放满时,X+1层不能有节点,且没有满的层的节点必须是从左往右排布的。

我们可以借助层序遍历来判断二叉树是不是完全二叉树。
步骤为:就算节点为空也要入队列,直到出队列时遇到了空节点时,开始连续出队列,如果队列中剩下的元素中有非空的节点,就说明不是完全二叉树

我们以这棵二叉树为例:
2
当轮到4的左孩子(尽管不存在,但先这么理解一下)出队列时,开始不再入队列,只出队列并判断是否为空,这时队列中还有4的右节点7,那就判断出来了二叉树不是完全二叉树。

如果在第一次出队列的为空节点的之后的节点都是空节点(比如上面这个二叉树没有节点7),那就是完全二叉树。

bool BinaryTreeComplete(BTNode* root)
{assert(root);Queue qu;QueueInit(&qu);QueuePush(&qu, root);while (!QueueEmpty(&qu)){BTNode* tmp = QueueFront(&qu);//如果出队列的是空节点,就停止这个循环if (!tmp)break;QueuePop(&qu);QueuePush(&qu, tmp->left);QueuePush(&qu, tmp->right);}//这个循环只出不入while (!QueueEmpty(&qu)){//如果队列中还有空节点,就说明不是完全二叉树if (QueueFront(&qu)){QueueDestroy(&qu);return false;}QueuePop(&qu);}QueueDestroy(&qu);return true;
}

3. 二叉树性质

  1. 对任何一棵二叉树,如果度为 0 的叶结点个数为n0,度为 2 的分支结点个数为n2 ,则有
    n0 = n2+1
    2
    证明:
    假设一个二叉树有 a 个度为 2 的节点,b 个度为 1 的节点,c 个叶节点,则这个二叉树的边数是 2a+b
    另一方面,由于共有 a+b+c 个节点,所以边数等于 a+b+c-1
    结合上面两个公式:
    即 2a+b = a+b+c-1,即 a=c-1

题目练习

  1. 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )
    A 不存在这样的二叉树
    B 200
    C 198
    D 199
    有199个度为2的节点,那么在二叉树中,度为0的节点有199+1也就是200个,选B。

  2. 在具有 2n 个结点的完全二叉树中,叶子结点个数为()
    A n
    B n+1
    C n-1
    D n/2
    设叶子节点有x个,那么度为2的节点为x-1个。有2n个节点,所以度为1的节点有2n-2x+1条,而完全二叉树中度为1的节点只有0或1个,但是这个二叉树的结点个数为偶数(由于根节点有1个,而其他的除了最后一层的结点个数都是偶数),所以度为1的结点个数为1。解方程2n-2x+1=1就可以得出x=n。选A。

  3. 一棵完全二叉树的结点数位为531个,那么这棵树的高度为()
    A 11
    B 10
    C 8
    D 12
    完全二叉树每一层的节点个数为2n-1个,那么根据等比数列的求和公式,完全二叉树前n层的节点个数为2n-1,解方程2n-1>=531可得,n为10,选B。

  4. 一个具有767个结点的完全二叉树,其叶子结点个数为()
    A 383
    B 384
    C 385
    D 386
    设叶子节点有x个,那么度为2的节点为x-1个,度为1的节点为768-2x个,通过第2题的分析我们可知度为1的节点为0个,就可以算出x=384,选B。

数据结构初阶的二叉树就到这里,想必你会发现这个二叉树我们没有实现插入删除的接口,因为二叉树的插入删除使用C语言实现过于复杂,会在高阶数据结构中讲解。

谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章

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

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

相关文章

SpringCloud从零开始简单搭建 - JDK17

文章目录 SpringCloud Nacos从零开始简单搭建 - JDK17一、创建父项目二、创建子项目三、集成Nacos四、集成nacos配置中心 SpringCloud Nacos从零开始简单搭建 - JDK17 环境要求:JDK17、Spring Boot3、maven。 那么,如何从零开始搭建一个 SpringCloud …

Qt构建JSON及解析JSON

目录 一.JSON简介 JSON对象 JSON数组 二.Qt中JSON介绍 QJsonvalue Qt中JSON对象 Qt中JSON数组 QJsonDocument 三.Qt构建JSON数组 四.解析JSON数组 一.JSON简介 一般来讲C类和对象在java中是无法直接直接使用的,因为压根就不是一个规则。但是他们在内存中…

使用 Internet 共享 (ICS) 方式分配ip

设备A使用dhcp的情况下,通过设备B分配ip并共享网络的方法。 启用网络共享(ICS)并配置 NAT Windows 自带的 Internet Connection Sharing (ICS) 功能可以简化 NAT 设置,允许共享一个网络连接给其他设备。 打开网络设置&#xff1…

灵当CRM系统index.php存在SQL注入漏洞

文章目录 免责申明漏洞描述搜索语法漏洞复现nuclei修复建议 免责申明 本文章仅供学习与交流,请勿用于非法用途,均由使用者本人负责,文章作者不为此承担任何责任 漏洞描述 灵当CRM系统是一款功能全面、易于使用的客户关系管理(C…

jacoco生成单元测试覆盖率报告

前言 单元测试是日常编写代码中常用的,用于测试业务逻辑的一种方式,单元测试的覆盖率可以用来衡量我们的业务代码经过测试覆盖的比例。 目前市场上开源的单元测试覆盖率的java插件,主要有Emma,Cobertura,Jacoco。具体…

2025年最新大数据毕业设计选题-Hadoop综合项目

选题思路 回忆学过的知识(Python、Java、Hadoop、Hive、Sqoop、Spark、算法等等。。。) 结合学过的知识确定大的方向 a. 确定技术方向,比如基于Hadoop、基于Hive、基于Spark 等等。。。 b. 确定业务方向,比如民宿分析、电商行为分析、天气分析等等。。。…

Docker操作MySQL

1,拷贝; docker cp mysql01:/etc/mysql .2,修改conf.d和mysql.conf.d文件 3, vim mysql/my.cnf 4,拷贝并替换my.cnf文件 5,mysql镜像重启命令: docker exec -it mysql01 -uroot -p0000006&…

MySQL篇(运算符)(持续更新迭代)

目录 一、简介 二、运算符使用 1. 算术运算符 1.1. 加法运算符 1.2. 减法运算符 1.3. 乘法与除法运算符 1.4. 求模(求余)运算符 2. 比较运算符 2.1. 等号运算符 2.2. 安全等于运算符 2.3. 不等于运算符 2.4. 空运算符 2.5. 非空运算符 2.6.…

万字长文——ConvNeXt(2022CVPR),卷积网络的顶峰之作,在Transformer盛行的当下,卷积网络还能再战!

ConvNext:A ConvNet for the 2020s ConvNext:2020 年代的卷积神经网络 论文地址: https://arxiv.org/pdf/2201.03545 自从Transformer成功应用在视觉领域并且取得显著成绩后,很多人开始抛弃卷积网络架构,转而使用Transformer。然而有的大佬不认为卷积过时了,于是有了这篇…

【工具变量】科技金融试点城市DID数据集(2000-2023年)

时间跨度:2000-2023年数据范围:286个地级市包含指标: year city treat post DID(treat*post) 样例数据: 包含内容: 全部内容下载链接: 参考文献-pdf格式:https://…

[Visual Stuidio 2022使用技巧]2.配置及常用快捷键

使用vs2022开发WPF桌面程序时常用配置及快捷键。 语言:C# IDE:Microsoft Visual Studio Community 2022 框架:WPF,.net 8.0 一、配置 1.1 内联提示 未开启时: 开启后: 开启方法: 工具-选…

每日刷题(算法)

我们N个真是太厉害了 思路: 我们先给数组排序,如果最小的元素不为1,那么肯定是吹牛的,我们拿一个变量记录前缀和,如果当前元素大于它前面所有元素的和1,那么sum1是不能到达的值。 代码: #def…

python - 子类为什么调用父类的方法

菜鸟教程 - 面向对象https://www.runoob.com/python3/python3-class.html为什么写这个呢 ,因为很多时候,事情很简单,但我往往记住了使用方式,忘记了使用原因,也因为自己看到super()时,也想问为什么要用supe…

AJAX(一)HTTP协议(请求响应报文),AJAX发送请求,请求问题处理

文章目录 一、AJAX二、HTTP协议1. 请求报文2. 响应报文 三、AJAX案例准备1. 安装node2. Express搭建服务器3. 安装nodemon实现自动重启 四、AJAX发送请求1. GET请求2. POST请求(1) 配置请求体(2) 配置请求头 3. 响应JSON数据的两种方式(1) 手动,JSON.parse()(2) 设置…

Python 实现Excel XLS和XLSX格式相互转换

在日常工作中,我们经常需要处理和转换不同格式的Excel文件,以适应不同的需求和软件兼容性。Excel文件的两种常见格式是XLS(Excel 97-2003)和XLSX(Excel 2007及以上版本)。本文将详细介绍如何使用Python在XL…

【数据结构篇】~链表算法题3(环形链表)

链表算法题3(环形链表) 环形链表的证明1. 环形链表I​1) 思路2)代码实现 2. 环形链表II​1) 思路11) 思路22)代码实现 3. 随机链表的复制​1) 思路2)代码实现 环形链表的证明 1. 环形链表I​ https://leetcode.cn/prob…

Redhat 7,8系(复刻系列) 一键部署Oracle21c-xe rpm

Oracle21c-xe前言 无论您是开发人员、DBA、数据科学家、教育工作者,还是仅仅对数据库感兴趣,Oracle Database Express Edition (XE) 都是理想的入门方式。它是全球企业可依赖的强大的 Oracle Database,提供简单的下载、易于使用和功能齐全的体验。您可以在任何环境中使用该…

git push出错Push cannot contain secrets

报错原因: 因为你的代码里面包含了github token明文信息,github担心你的token会泄漏,所以就不允许你推送这些内容。 解决办法: 需要先把代码里面的github token信息删除掉,并且删掉之前的历史提交,只要包…

Stable Diffusion Fooocus批量绘图脚本

当当当挡~,流动传热数值计算之余发布点AIGC相关文章,希望大家能喜欢~ 1 Stable Diffusion各种UI分析对比 提示:此部分主要是对SD各种界面的简要介绍和对比,只关注Fooocus批量绘图的读者可直接跳到第二部分。 Stable Diffusion …

微服务_入门2

文章目录 一、Feign远程调用二、Gateway服务网关2.1、为什么需要网关2.2、gateway快速入门2.3、断言工厂2.4、过滤器 一、Feign远程调用 来看我们以前利用RestTemplate发起远程调用的代码: 存在下面的问题: 代码可读性差,编程体验不统一&…