【数据结构和算法】--- 二叉树(3)--二叉树链式结构的实现(1)
目录
- 一、二叉树的创建(伪)
- 二、二叉树的遍历
- 2.1 前序遍历
- 2.2 中序遍历
- 2.3 后序遍历
- 三、二叉树节点个数及高度
- 3.1 二叉树节点个数
- 3.2 二叉树叶子节点个数
- 3.3二叉树第k层节点个数
- 3.4 二叉树查找值为x的节点
- 四、二叉树的创建(真)
一、二叉树的创建(伪)
在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,且为了方便后面的介绍,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。
基于二叉树的链式结构,于是可以先malloc
动态开辟出二叉树的每个节点并初始化,然后通过节点中的指针struct BinaryTreeNode* left
(指向左树)和struct BinaryTreeNode* right
(指向右树),将各个节点连接起来,最后大致模拟出了一棵二叉树,代码如下:
typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left; //左树struct BinaryTreeNode* right; //右树
}BTNode;
BTNode* CreatBinaryTree()
{//动态开辟节点BTNode* node1 = BuyNode(1);BTNode* node2 = BuyNode(2);BTNode* node3 = BuyNode(3);BTNode* node4 = BuyNode(4);BTNode* node5 = BuyNode(5);BTNode* node6 = BuyNode(6);//链接节点node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;return node1;
}
在实现二叉树基本操作前,再回顾下二叉树的概念,一棵二叉树是结点的一个有限集合,该集合:
- 或者为空
- 由一个根节点加上两棵分别称为左子树和右子树的二叉树组成
二叉树满足的条件:
- 二叉树不存在度大于2的结点;
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。
其余二叉树的概念还请回顾:【数据结构和算法】—二叉树(1)–树概念及结构
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。
注意: 上述代码并不是创建二叉树的方式,真正创建二叉树方式将在后面介绍。
二、二叉树的遍历
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。 访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。下图可以理解为是二叉树的前序遍历:
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
- 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
- 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
- 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。 NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
2.1 前序遍历
前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。 依据此规律我们便可如下遍历二叉树,为了方便观察,每次遍历到根节点都输出一下:
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{if (root == NULL){putchar('N');return;}putchar(root->val); //访问根节点BinaryTreePrevOrder(root->left); //访问左子树BinaryTreePrevOrder(root->right); //访问右子树
}
前序遍历代码,逻辑结构大致如下图,可以参考一下:
在这利用递归思想来解决前序遍历的问题,因为是前序遍历(访问顺序依次是根节点,左子树,右子树),所以在进入下层递归前可以先输出根节点。当进入左/右子树时,会更新根节点(原为上层根节点的左/右孩子节点) 。二叉树的叶子节点的左右孩子都为NULL
,那么便可将递归的结束条件定为NULL
。这便是前序遍历的递归逻辑。
2.2 中序遍历
中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。 与前序遍历相似,只是访问顺序不同(依次是左子树,根节点,右子树),那么调整一下代码顺序即可,将输出根节点值的操作(putchar(root->val);
),放在访问左子树之后。那么递归每次都会先进入左子树,且最先打印的为叶子节点。代码如下:
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{if (root == NULL){putchar('N');return;}BinaryTreeInOrder(root->left); //访问左子树putchar(root->val); //输出根节点BinaryTreeInOrder(root->right); //访问右子树
}
2.3 后序遍历
后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。 同样与前序遍历相似,访问顺序不同(依次是左子树,右子树,根节点),依此特性所以我们只需将输出操作(putchar(root->val
))放到最后,其余代码不变。实现思想-递归完左子树和右子树后再输出根节点。 代码如下:
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{if (root == NULL){putchar('N');return;}BinaryTreePostOrder(root->left); //访问左子树BinaryTreePostOrder(root->right); //访问右子树putchar(root->val); //输出根节点
}
三种遍历最后输出的结果(图中N
表示递归时遇到了NULL
):
- 前序遍历结果:1 2 3 4 5 6
- 中序遍历结果:3 2 1 5 4 6
- 后序遍历结果:3 2 5 6 4 1
- 设一课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为 ( D )。
A adbce
B decab
C debac
D abcde解: 解题思想(给定两种遍历序列,求出另外一种):我们可以根据各种遍历方法的特性来求解。( 1 ) 根据后续遍历序列找出根节点,因为后续遍历最后才会输出根,那么在序列的最后一个即为根节点
a
;( 2 ) 接着在中序遍历序列中找出根节点,然后划分左子树和右子树;( 3 ) 然后再到后序遍历序列中去除左子树和根节点,那么得到的便是右子树,并将其看为独立的树;( 4 ) 重复上述三步操作,直到排出完整的树。
图解如下:
解决此类问题最重要的还是要弄懂代码的递归思想。
三、二叉树节点个数及高度
3.1 二叉树节点个数
求二叉树的节点个数,利用的还是递归思想。我们可以将问题转化为----根节点(1
),左子树的节点个数(root->left
)和右子树的节点个数(root->right
)的总结点个数。我们可以将根节点为空(root == NULL
)作为递归结束的条件,并返回0
(return 0
)。 这种方法通常被称为递归分治。
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{if (root == NULL)return 0;return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
代码图解:
3.2 二叉树叶子节点个数
叶子节点概念:含有的子树个数为0的节点。 如本次创建的二叉树的节点3,节点5,节点6。
基于叶子节点的特性,同样可以利用递归分治的方法,将问题同化为----左子树的叶子节点个数和右子树的叶子节点个数之和。
函数返回的条件:
- 当前节点(
root
)的左子树(root->left
)和右子树(root->right
)都为空时(即!(root->left && root->right)
),那么此节点为叶子节点,并返回1
; - 当前节点为空节点(即
(root == NULL)
),返回0
; - 函数执行到最后,返回当前树的叶子节点数。
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{if (root == NULL)return 0;if (!(root->left && root->right))return 1;return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
代码图解:
3.3二叉树第k层节点个数
既然是求第k
层的节点个数,那我们便可以定义一个变量k
,记录当前函数所要递归的层数。既然k
的值是变化的,那么可以将他作为函数的参数,每递归一层便让他减一k - 1
,那么当k
的值到1
时,便是我们所要求的二叉树的第k
层。
依据上述关系,便可以得出 函数返回的条件:
- 当遇到空节点时(
root == NULL
),返回0
; - 当
k == 1
时(说明到了二叉树的第k
层),且当前节点不为空(root != NULL
),那么便可返回1
; - 函数执行到了最后,返回统计到的符合条件的节点个数。
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL)return 0;if (k == 1 && root != NULL)return 1;return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
根据函数返回条件不难发现,当k == 1
时递归便不会继续往下层执行,这是因为此时函数必定会满足两个if
条件中的一个,这也避免了递归到二叉树的第k + 1
层。
代码图解:
3.4 二叉树查找值为x的节点
查找值为x
的节点,可以将递归分治为----判断当前节点,判断左子树,判断右子树。 那么当遇到空节点(root == NULL
)就返回return NULL
;如果遇到所要查找的值(root->val == x
)就返回当前地址(return root
);那么如果都不满足就继续搜寻左子树,然后右子树;直到最后搜寻完整棵二叉树,都没有找到x
,那么便返回NULL
。
还需要注意的一个问题是,如果在递归过程中找到了目标值x
,返回了当前地址root
,但是现在只是回到了上一层递归的地方,返回值并不会被接收,而是继续执行下一个逻辑。 那么我们便可以用BTNode* ret1
来接受函数的返回值,并判断,当返回值(ret1
)不为NULL
时(即说明上一次递归时,找到了x
)直接返回此值return ret1
。
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{//空节点if (root == NULL)return NULL;//值为x的节点if (root->val == x)return root;//左子树递归,ret接收返回值,并判断BTNode* ret1 = BinaryTreeFind(root->left, x);if (ret1 != NULL)return ret1;//方法一:易于理解//BTNode* ret2 = BinaryTreeFind(root->right, x);//if (ret2 != NULL)// return ret2;return BinaryTreeFind(root->right, x);return NULL;
}
代码图解:
四、二叉树的创建(真)
通过上面对各种遍历方法和递归思想的讲解,相信使用递归来真正创建二叉树也不难了,如下:
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{//判断是否为空,即当a[*pi] == '#'时if (a[*pi] == '#'){(*pi)++;return NULL;}//动态开辟节点BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc()::fail");exit(-1);}//赋值并连接(递归)node->val = a[*pi];(*pi)++;node->left = BinaryTreeCreate(a, pi);node->right = BinaryTreeCreate(a, pi);retur
上面介绍的二叉树的一些函数的执行结果如下:
另外还有一些较为复杂的函数将在下一篇文章中介绍。
相关文章:

【数据结构和算法】--- 二叉树(3)--二叉树链式结构的实现(1)
目录 一、二叉树的创建(伪)二、二叉树的遍历2.1 前序遍历2.2 中序遍历2.3 后序遍历 三、二叉树节点个数及高度3.1 二叉树节点个数3.2 二叉树叶子节点个数3.3二叉树第k层节点个数3.4 二叉树查找值为x的节点 四、二叉树的创建(真) 一、二叉树的创建(伪) 在学习二叉树的基本操作前…...

Cesium for Unity包无法加载
太上老君急急如律⚡令⚡ 🥙关闭UnityHub🧀启动梯子🥪cmd 启动UnityHub 🥙关闭UnityHub 🧀启动梯子 🥪cmd 启动UnityHub 把批处理启动文件👈中的exe的路径换成自己的安装目录!保存…...

Leetcode—40.组合总和II【中等】
2023每日刷题(七十七) Leetcode—40.组合总和II 算法思想 实现代码 class Solution { public:vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {vector<vector<int>> ans;vector<int…...

vscode连不上虚拟机,一直密码错误
最近在做毕设,但是vscode使用连接不上虚拟机,我以为是网络配置的问题,一顿查阅没找到原因。 后来查了一下ssh的日志,发现ssh有消息,但是也提示密码错误。 没找到密码配置格式什么的,经查看sshd配置文件发现…...

力扣每日一题 --- 972. 相等的有理数
本题中的一个难点是怎么判断是否相等,如果自己写判断的话是不是很麻烦,判断整数之后再去判断小数部分,那么我们这题的另一个难点就要登场了,第一个难点让本题的情况变得复杂,第二个难点让本题变得很难想到怎么判断&…...

EXECL 单元格字符串链接 CONCAT :应用:将一行数据转为json
源: 目标 函数表示 CONCAT("data", CHAR(10), "{", CHAR(10), " ", "ulAlarmId : ", A5, CHAR(10), " ", "ulAlarmLevel : ", D5, CHAR(10)," ", "bBo…...

基于Python实现人脸识别相似度对比
目录 引言背景介绍目的和意义 人脸识别的原理人脸图像获取人脸检测与定位人脸特征提取相似度计算 基于Python的人脸相似度对比实现数据集准备人脸图像预处理特征提取相似度计算 引言 背景介绍 人脸识别技术是一种通过计算机对人脸图像进行分析和处理,从而实现自动识…...

CSS 蜡烛效果
<template><view class="holder"><!-- 身子 --><view class="candle"><!-- 光源 --><view class="blinking-glow"></view><!-- 火星子 --><view class="thread"></view>…...

渗透测试之Kali如何利用CVE-2019-0708漏洞渗透Win7
环境: 1.攻击者IP:192.168.1.10 系统: KALI2022(vmware 16.0) 2.靶机IP:192.168.1.8 系统:Windows 7 6.1.7601 Service Pack 1 Build 7601 已开启远程协助RDP服务开启了3389端口 问题描述: KALI 如何利用CVE-2019-0708漏洞渗透Win7 解决方案: 1.打开kali,msf搜索…...

Docker(二)安装指南:主要介绍在 Linux 、Windows 10 和 macOS 上的安装
作者主页: 正函数的个人主页 文章收录专栏: Docker 欢迎大家点赞 👍 收藏 ⭐ 加关注哦! 安装 Docker Docker 分为 stable test 和 nightly 三个更新频道。 官方网站上有各种环境下的 安装指南,这里主要介绍 Docker 在…...
LeetCode 410. 分割数组的最大值
一、题目 1、题目描述 给定一个非负整数数组 nums 和一个整数 k ,你需要将这个数组分成 k 个非空的连续子数组。 设计一个算法使得这 k 个子数组各自和的最大值最小。 2、接口描述 class Solution { public:int splitArray(vector<int>& nums, int …...
linux shell脚本 基础认识
Linux 系统中的 Shell 是一个特殊的应用程序,它介于操作系统内核与用户之间,充当 了一个“命令解释器”的角色,负责接收用户输入的操作指令(命令)并进行解释,将需要执行的操作传递给内核执行,并…...

一文(10图)了解Cornerstone3D核心概念(万字总结附导图)
Cornerstone3D介绍 Cornerstone3D是一个专门为处理三维医学影像而设计的JavaScript库。 它是Cornerstone项目的一部分,旨在为医学影像社区提供高性能、可扩展且易于使用的开源Web工具,专注于提供交互式的3D医学图像浏览体验,适用于多种医学…...

牛客网-----跳石头
题目描述: 一年一度的“跳石头”比赛又要开始了! 这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有N块岩石(不含起点和终点的岩石)。在比赛过程中࿰…...

用ChatGPT教学、科研!大学与OpenAI合作
亚利桑那州立大学(简称“ASU”)在官网宣布与OpenAI达成技术合作。从2024年2月份开始,为所有学生提供ChatGPT企业版访问权限,主要用于学习、课程作业和学术研究等。 为了帮助学生更好地学习ChatGPT和大语言模型产品,AS…...

运维平台介绍:视频智能运维平台的视频质量诊断分析和告警中心
目 录 一、视频智能运维平台介绍 (一)平台概述 (二)结构图 (三)功能介绍 1、运维监控 2、视频诊断 3、巡检管理 4、告警管理 5、资产管理 6、工单管理 7、运维…...

GAMES104-现代游戏引擎:从入门到实践 - 物理引擎课程笔记汇总
文章目录 0 入门资料1 物理引擎基本概念Actor & shapesRigid body dynamicsCollision DetectionCollision Resolution 应用与实践Character controllerRagdoll 0 入门资料 GAMES104-现代游戏引擎:从入门到实践_课程视频_bilibiliGAMES104官方账号 - 知乎课程主页…...
【linux】Xorg的工作原理
介绍 在linux系统上执行nvidia-smi时,总有一个进程占用gpu。 1 N/A N/A 2174 G /usr/lib/xorg/Xorg 4MiB /usr/lib/xorg/Xorg 是与X Window System(简称X11或X)相关的一个应用程序。X Window System是一个…...
02-docker下部署seata
官方部署文档 http://seata.io/zh-cn/docs/ops/deploy-by-docker 配置参数说明 http://seata.io/zh-cn/docs/user/configurations 1、镜像拉取 docker pull seata-server2、复制配置文件 mkdir /home/server/seata cd /home/server/seata docker run -d -p 8091:8091 -p 709…...

回归预测 | Matlab实现GA-APSO-MBP、GA-MBP、MBP、BP多输入单输出回归预测
回归预测 | Matlab实现GA-APSO-MBP、GA-MBP、MBP、BP多输入单输出回归预测 目录 回归预测 | Matlab实现GA-APSO-MBP、GA-MBP、MBP、BP多输入单输出回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab实现GA-APSO-MBP、GA-MBP、MBP、BP多输入单输出回归预测&…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...