【数据结构与算法】详解二叉树下:实践篇————通过链式结构深入理解并实现二叉树

           💓 博客主页:倔强的石头的CSDN主页 

           📝Gitee主页:倔强的石头的gitee主页

            ⏩ 文章专栏:《数据结构与算法》

                                  期待您的关注

1b7335aca73b41609b7f05d1d366f476.gif

目录

一、二叉树前置知识

二、二叉树链式结构实现的结构定义

三、二叉树的基本实现

🍃创建

🍃销毁

四、二叉树的遍历

🍃前序遍历

🍃中序遍历

🍃后序遍历

🍃层序遍历

五、二叉树的扩展功能

🍃求节点个数

🍃求叶子节点个数

🍃求第K层节点个数

🍃求二叉树的高度

🍃查找值为X的节点


在计算机科学中,二叉树是一种重要的数据结构,它以其独特的结构和性质在数据存储、搜索和算法设计中发挥着重要作用。链式结构作为二叉树的一种常见表示方式,通过节点间的指针连接,实现了对二叉树的高效存储和访问。

一、二叉树前置知识

参考前置文章:二叉树理论篇

【数据结构与算法】详解二叉树 上:理论篇——二叉树的基本概念与性质-CSDN博客

二、二叉树链式结构实现的结构定义

二叉树的每个节点包括三个部分:

  • 数据部分,这里称为data
  • 左指针,指向节点的左孩子,这里称为left指针。如果左孩子为空,则指向NULL
  • 右指针,指向节点的右孩子,这里称为right指针。如果右孩子为空,则指向NULL

结构如下图所示:

 结构定义如下:

typedef char BTDataType;//数据类型重命名typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;//结构体重命名

三、二叉树的基本实现

🍃创建

因为使用链式结构实现,所以二叉树不需要初始化,只需定义好单个申请节点的函数,每次插入节点调用节点创建函数即可

BTNode* BuyNode(BTDataType x)//申请新节点
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("newnode\n");exit(1);}newnode->data = x;newnode->left = newnode->right = NULL;return newnode;
}

二叉树的一般插入意义不大,跟手动插入无异,后面会在搜索二叉树篇章讲解二叉树插入的实现

🍃销毁

递归调用完成销毁,每棵树先释放左孩子和右孩子,再返回去销毁根,因此使用后序遍历的方法最为合适(不需要记录孩子,只需要正常遍历销毁即可)

后序遍历,文章的后面便会介绍,如果不太理解,可以直接点击目录查看遍历部分

调用函数
如果节点为空,直接返回
如果节点非空

  • 先调用函数释放左子树
  • 再调用函数释放右子树
  • 最后释放节点自身

void BinaryTreeDestory(BTNode* root)//后序遍历并销毁
{if (root== NULL)return;BinaryTreeDestory(root->left);//左BinaryTreeDestory(root->right);//右free(root);//根
}

四、二叉树的遍历

二叉树的遍历是二叉树的基本操作之一,它指的是按照某种规则访问二叉树中的所有节点,并且每个节点只被访问一次。二叉树的遍历方式有多种,其中最常见的有四种:前序遍历(Pre-order Traversal)、中序遍历(In-order Traversal)、后序遍历(Post-order Traversal)和层序遍历(Level-order Traversal)。

  • 前序遍历(根 左 右)
  • 中序遍历(左 根 右)
  • 后序遍历(左 右 根)
  • 层序遍历(逐层访问)

这里以每次遍历节点打印节点数据为例(指针走到空打印N)

🍃前序遍历

使用递归的方法实现:

  • 访问根节点
  • 前序遍历左子树
  • 前序遍历右子树


接收一个指针(地址)
指针从二叉树的根结点开始遍历


递归的结束条件: 指针指向空,则打印N,return
不满足递归条件,向深处递推:
1. 打印指针指向的节点的数据(相当于访问根节点)
2. 调用函数自己,传递节点左孩子的地址作为参数(相当于访问左子树)
3.调用函数自己,传递节点右孩子的地址作为参数(相当于访问右子树)

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{if (root == NULL){printf("N");return;}printf("%c", root->data);BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);}

🍃中序遍历

使用递归的方法实现:

  • 中序遍历左子树
  • 访问根节点
  • 中序遍历右子树


接收一个指针(地址)
指针从二叉树的根结点开始遍历


递归的结束条件:指针指向空,则打印N,return
不满足递归条件,向深处递推:
1. 调用函数自己,传递节点左孩子的地址作为参数(相当于访问左子树)
2.打印本节点的数据(相当于访问根节点)
3.调用函数自己,传递节点右孩子的地址作为参数(相当于访问右子树)

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{if (root == NULL){printf("N");return;}BinaryTreeInOrder(root->left);printf("%c", root->data);BinaryTreeInOrder(root->right);
}

🍃后序遍历

使用递归的方法实现:

  • 后序遍历左子树
  • 后序遍历右子树
  • 访问根节点


接收一个指针(地址)
指针从二叉树的根结点开始遍历


递归的结束条件:指针指向空,则打印N,return
不满足递归条件,向深处递推:
1. 调用函数自己,传递节点左孩子的地址作为参数(相当于访问左子树)
2.调用函数自己,传递节点右孩子的地址作为参数(相当于访问右子树)
3.打印本节点数据(相当于访问根节点)

void BinaryTreePostOrder(BTNode* root)
{if (root == NULL){printf("N");return;}BinaryTreePostOrder(root->left);BinaryTreePostOrder(root->right);printf("%c", root->data);}

🍃层序遍历

层序遍历(Level-order Traversal)

  • 从根节点开始,按层次从上到下、从左到右遍历二叉树

通常使用队列(Queue)来实现层序遍历。


如果树不为空,将它的根结点的地址作为数据,入队列


对队列的节点访问:
每次取队首的节点访问,将它的地址记录下来并出队列,同时将它的左右孩子(如果存在的话)入队列
当队列为空时,二叉树的层次遍历完成

 层次遍历直接借用了以前已经实现好的队列,详细内容可以参考前置文章

【数据结构与算法】使用单链表实现队列:原理、步骤与应用_利用循环单链表(如下图)模拟实现队列操作,请给出入队enqueue过程,要求时间复-CSDN博客

 

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{Queue queue;QueueInit(&queue);//队列初始化if (root)//树的根节点入队列QueuePush(&queue, root);while (!QueueEmpty(&queue))//结束条件是队列为空{BTNode* tmp = QueueFront(&queue);//取队首元素打印数据并出队列printf("%c", tmp->data);QueuePop(&queue);//左右子树若不为空,入队列if (tmp->left)QueuePush(&queue, tmp->left);if (tmp->right)QueuePush(&queue, tmp->right);}printf("\n");QueueDestroy(&queue);
}

五、二叉树的扩展功能

🍃求节点个数

两种方式:

方式一:计数器方式
这种方式需要考虑的坑比较多——想要求得树的节点个数就得递归调用,所以用局部变量计数不可行(每次调用都会重置变量),静态局部变量也不可行(一次调用可行,多次调用会将值累加)

全局变量可行,但得在外部每次调用的时候对全局变量置零
指针的方式也可行:实参多传递一个变量的地址,形参用指针接收,多次调用也需要重新创建变量或置零

这两种方式的实现思路:
如果节点为空,返回
否则计数器++
再调用函数对左子树求个数
调用函数对右子树求值
函数不需要返回值,会通过全局变量/指针带出

方式一并不推荐,实现复杂容易出错,所以这里就不给出实现代码了、

方式二:  递归方式(推荐)

  • 递归调用相加对于给定的二叉树节点,如果节点为空(NULL),则返回0(表示没有节点)。
  • 否则,返回1(表示当前节点本身)加上左子树和右子树的节点数(递归调用)
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{if (root == NULL)return 0;return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

🍃求叶子节点个数

解决方式与求二叉树的节点数量类似,同样使用递归实现,只是细节略有差异:

递归调用:

  • 对于给定的二叉树节点,如果节点为空(NULL),则返回0(表示没有节点)。
  • 如果节点左右孩子都为空,返回1(表示当前节点为叶子节点)
  • 如果上面两个return都没有执行,说明该节点既不为空也不是叶子节点,返回左子树+右子树的叶子节点数(递归调用)
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{if (root == NULL)return 0;if (root->left == NULL && root->right == NULL)return 1;return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

🍃求第K层节点个数

实现思路:
用递归的思路将问题分解


假设根结点为第一层,那么对于第一层,要求的是第k层;对于第二层,要求的是第k-1层……逐层分解,对于第k层,求的就是第一层

具体实现逻辑:

  • 递归调用函数,参数有两个,分别是节点地址和k
  • 如果节点为空,返回0
  • 上述判断不成立,再判断如果k==1,返回1
  • 如果上述都没有执行,说明节点既不为空,也不是第k层
  • 递归调用函数,返回该节点的左孩子的k-1层的节点数+右孩子的k-1层的节点数(如果下一层k==1,则直接返回结果;如果不是,还会继续递归调用)
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL)return 0;if (k == 1)//走到这里,说明节点不为空return 1;return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);}

🍃求二叉树的高度

二叉树的高度(或深度)是指从根节点到最远叶子节点的最长路径上的节点数。一个空树的高度定义为0

对于非空二叉树,其高度可以通过递归的方式计算:

  • 如果树是空的,那么高度是0。
  • 否则,高度是左子树和右子树高度的最大值加1(加1是因为要算上根节点)。

 不过这里有一个坑要避免一下:

如果用三目操作符完成return语句,即:return( root->left)>(root->right)?( root->left)+1:(root->right)+1


在树比较大时,时间会出现极端的增长
原因是第一次计算的结果没有被记录下来,每次都要重复计算两次,但第一层计算两次,第二层就多计算2*2次,第三层计算2*2*2次,以此类推,将会是一个非常恐怖的计算量

解决方式也很简单:
如果树是空的,那么高度是0。

否则,先求得左右孩子的高度记录并记录下来,两个值比较,将较大的值+1返回

//求树的高度
int BinaryHeight(BTNode* root)
{if (root == NULL)return 0;int leftHeight = BinaryHeight(root->left);int rightHeight = BinaryHeight(root->right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

🍃查找值为X的节点

实现思路:
递归遍历二叉树,查找值为x的节点
有一个关键且容易被忽略的点,就是如何在递归调用的过程中,将查找到的节点的地址通过返回值带出来(因为是递归调用,所以必须让函数的第一层带出返回值)

实现方法:

 递归调用函数


如果节点为空,返回NULL
上述未执行,再判断节点的值是否为x,如果是的话,返回该节点的地址(注意此处返回只能返回给上一层,不能跳出整个函数)


如果上述都未执行,再进一步判断:
调用函数获取左子树返回的值,如果该值不为空,说明获得了值为x的节点的地址,将该值返回给上一层
如果调用左子树未返回值,再调用函数获取右子树返回的值,如果改值不为空,说明获得了值为x的节点的地址,将该值返回给上一层

上述表达式都未返回结果,说明查不到值为X的节点,返回NULL

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->data == x)return root;BTNode* n1 = BinaryTreeFind(root->left, x);if (n1)return n1;BTNode* n2 = BinaryTreeFind(root->right, x);if (n2)return n2;return NULL;
}

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

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

相关文章

仿哔哩哔哩视频app小程序模板源码

仿哔哩哔哩视频app小程序模板源码 粉色的哔哩哔哩手机视频网页,多媒体视频类微信小程序ui前端模板下载。包含:视频主页和播放详情页。 仿哔哩哔哩视频app小程序模板源码

【漏洞复现】方正全媒体采编系统——SQL注入

声明:本文档或演示材料仅供教育和教学目的使用,任何个人或组织使用本文档中的信息进行非法活动,均与本文档的作者或发布者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 方正全媒体采编系统(FZMediaEditor)是一…

短信群发平台适用于哪些行业?

短信群发平台作为一种高效、快速且成本相对较低的通信方式,适用于多个行业。以下是一些主要适用行业的概述: 1. 零售与电商行业 应用场景:零售和电商企业可以利用短信群发进行新品推广、促销信息发布、订单状态更新、物流跟踪通知等。 2. 金…

【ARMv8/v9 GIC 系列 1.7 -- GIC PPI | SPI | SGI | LPI 中断使能配置介绍】

请阅读【ARM GICv3/v4 实战学习 】 文章目录 GIC 各种中断使能配置PPIs(每个处理器私有中断)SPIs(共享外设中断)SGIs(软件生成的中断)LPIs(局部中断)GIC 各种中断使能配置 在ARM GICv3和GICv4架构中,不同类型的中断(如PPIs、SPIs、SGIs和LPIs)可以通过不同的方式进…

springboot331+vue“有光”摄影分享网站系统+论文+源码+讲解

第3章 系统分析 3.1 可行性分析 3.1.1技术可行性 研发设计程序流程挑选面向对象设计、功能齐全、简单实用的Java编程设计核心理念。MySQL数据库存储数据。Idea工具作为编程软件,win10计算机操作系统作为应用系统,以及数据库可视化工具等技术职称。一般…

STM32自己从零开始实操08:STM32主控原理图

由于老师使用的各引脚分门别类的单片机原理图我没有找到,我使用是引脚按顺序摆放的,不方便一个模块一个模块截图展示,所以这部分使用老师的原理图。 一、电源 1.1电源的介绍 1.1.1数字电源和地(VDD和VSS) 数字电源…

FlinkCDC-3.1.1 DataStream Source

问题&#xff1a; Caused by: java.lang.ClassNotFoundException: org.apache.flink.table.catalog.ObjectPath 解决&#xff1a; 在poml文件中&#xff0c;导入的flink-table依赖把“ <scope>”去掉 <properties><maven.compiler.source>8</maven.compi…

【MySQL】mysqldumpslow工具 -- 总结慢查询日志文件

1. 作用 在平时使用MySQL数据库时&#xff0c;经常进行查询操作&#xff0c;有些查询语句执行的时间非常长&#xff0c;当执行时间超过设定的阈值时&#xff0c;我们称这个查询为慢查询&#xff0c;慢查询的相关信息通常需要用日志记录下来称为慢查询日志&#xff0c;mysqldum…

“未来已来·智能共融”高峰论坛在京成功举办

在人工智能技术的澎湃浪潮中,其与传统产业的深度融合正逐步成为驱动区域经济增长的新引擎。2024年7月4号,一场以“未来已来智能共融——探索人类智能与人工智能共生共进的新路径”为主题的高峰论坛在北京电子科技职业学院图书馆圆满落幕,为北京经济技术开发区(简称“北京经开区…

Django动态页面

一步一步跟着我理清楚。 一、在所有app之外创建templates&#xff0c;里面放的base.html是模板文件 base.html里面的具体代码如下&#xff0c;最重要的是这个地方content属于之后可动态替换的地方。 而这个load static 加载静态则代表一下全是固定的静态页面。 {% load static…

高可用hadoop分布式节点的扩容

解决方案 修改hdfs-site.xml 文件 原xml文件 <?xml version"1.0" encoding"UTF-8"?> <?xml-stylesheet type"text/xsl" href"configuration.xsl"?> <!--Licensed under the Apache License, Version 2.0 (th…

业务发展中 10 个最佳的 OKR 示例

业务发展是推动组织增长、培养合作伙伴关系和扩大市场覆盖范围的重要职能。目标和关键结果 (OKR) 可以作为推动业务发展工作和实现战略目标的强大工具。在这里&#xff0c;我们展示了业务发展中的十个最佳 OKR 示例&#xff0c;为旨在在该领域脱颖而出并实现其增长目标的组织提…

SpringMVC源码解析(一):web容器启动流程

SpringMVC源码系列文章 SpringMVC源码解析(一)&#xff1a;web容器启动流程 目录 一、SpringMVC全注解配置1、pom文件2、web容器初始化类(代替web.xml)3、SpringMVC配置类(代替springmvc.xml)4、测试Controller 二、SpringServletContainerInitializer1、web容器初始化入口2、…

【人工智能】-- 智能家居

个人主页&#xff1a;欢迎来到 Papicatch的博客 课设专栏 &#xff1a;学生成绩管理系统 专业知识专栏&#xff1a; 专业知识 文章目录 &#x1f349;引言 &#x1f349;基于深度卷积神经网络的表情识别 &#x1f348;流程图 &#x1f348;模型设计 &#x1f34d;网络架…

大厂面试官问我:Redis缓存如果扛不住,该怎么办?【后端八股文十一:Redis缓存八股文合集(1)】

本文为【Redis分布式锁八股文合集&#xff08;2&#xff09;】初版&#xff0c;后续还会进行优化更新&#xff0c;欢迎大家关注交流~ hello hello~ &#xff0c;这里是绝命Coding——老白~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&…

C++ | Leetcode C++题解之第22题完全二叉树的节点个数

题目&#xff1a; 题解&#xff1a; class Solution { public:int countNodes(TreeNode* root) {if (root nullptr) {return 0;}int level 0;TreeNode* node root;while (node->left ! nullptr) {level;node node->left;}int low 1 << level, high (1 <&…

三级_网络技术_08_IP地址规划技术

1.如果内网的某Web服务器允许外网访问&#xff0c;并且该服务器NAT转换表如图所示&#xff0c;那么外网主机正确访问该服务器时使用的URL是()。 http://59.12.1.1:1423 http://135.2.2.1 http://135.2.2.1:5511 http://192.168.33.11:80 2.如果内网的某FTP服务器允许外网访…

基于大数据技术Hadoop的气象分析可视化大屏设计和实现

博主介绍&#xff1a;硕士研究生&#xff0c;专注于信息化技术领域开发与管理&#xff0c;会使用java、标准c/c等开发语言&#xff0c;以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年&#xff0c;拥有近12年的管理工作经验&#xff0c;拥有较丰富的技术架…

网络资源模板--Android Studio 外卖点餐App

目录 一、项目演示 二、项目测试环境 三、项目详情 四、完整的项目源码 原创外卖点餐&#xff1a;基于Android studio 实现外卖(点)订餐系统 非原创奶茶点餐&#xff1a;网络资源模板--基于 Android Studio 实现的奶茶点餐App报告 一、项目演示 网络资源模板--基于Android …