草庐IT

数据结构与算法之《二叉树》详解

Ggggggtm 2023-07-10 原文

标题:二叉树的思路及代码实现

作者:@Ggggggtm

寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景

文章目录

一、树的概念及结构

二、二叉树的概念及结构

2、1 二叉树的概念

2、2 二叉树的特点

2、3 二叉树的结构(图片)

2、4 特殊的二叉树

三、二叉树的代码及思路实现

3、1 二叉树的存储结构

3、1、1 二叉树的顺序存储结构

3、1、2 二叉树的链式存储结构

3、2 二叉树链式结构的实现

3、2、1 定义结构体

3、2、2 自定义一个二叉树

3、2、3 前序遍历

3、2、4 中序遍历

3、2、5 后序遍历

3、2、6 求树中节点的个数

3、2、7 求树中叶节点的个数

3、3 二叉树的性质


一、树的概念及结构

  二叉树是树的一种,所以在学习二叉树之前我们先了解一下树的概念及结构。

  树是一种 非线性 的数据结构,它是由 n n>=0 )个有限节点组成一个具有层次关系的集合。 把它 叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的
  • 有一个特殊的结点,称为根结点,根节点没有前驱结点;
  • 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1T2……Tm,其中每一个集Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以 0个或多个后继;
  • 因此,树是递归定义的。  

这里还有我们熟知的树中的一些概念:

  • 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6;
  • 叶节点或终端节点:度为 0 的节点称为叶节点; 如上图: B C H I... 等节点为叶节点;
  • 非终端节点或分支节点:度不为 0 的节点; 如上图: D E F G... 等节点为分支节点;
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图: A B 的父节点;
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图: B A 的孩子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图: B C 是兄弟节点;
  • 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为 6;
  • 节点的层次:从根开始定义起,根为第 1 层,根的子节点为第 2 层,以此类推;
  • 树的高度或深度:树中节点的最大层次; 如上图:树的高度为 4;
  • 节点的祖先:从根到该节点所经分支上的所有节点;如上图: A 是所有节点的祖先;
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是 A 的子孙;
  • 森林:由 m m>0 )棵互不相交的多颗树的集合称为森林;(数据结构中的学习并查集本质就是 一个森林);

  在这里我们要注意树与非树的区别是;

  • 子树是不相交的;
  • 除了根结点外,每个节点有且仅有一个父结点;
  • 一颗N个结点的树有N-1条边。

以下我给大家举几个树和非树的例子,可以先简单了解以下。

树:

非树:

二、二叉树的概念及结构

2、1 二叉树的概念

  一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

2、2 二叉树的特点

  • 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
  • 二叉树的子树有左右之分,其子树的次序不能颠倒。

2、3 二叉树的结构(图片)

2、4 特殊的二叉树

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉 树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对 于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号 从1n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

三、二叉树的代码及思路实现

3、1 二叉树的存储结构

   二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构

3、1、1 二叉树的顺序存储结构

  顺序结构存储就是使用 数组来存储 ,一般使用数组 只适合表示完全二叉树 ,因为不是完全二叉树 会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

3、1、2 二叉树的链式存储结构

  二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成数据域左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链。

3、2 二叉树链式结构的实现

  二叉树的链式结构实现都有哪些模块呢?接下来我简单的给大家总结一下:

  1. 定义结构体;
  2. 自定义一个二叉树;
  3. 前序遍历;
  4. 中序遍历;
  5. 后序遍历;
  6. 求树中节点的个数;
  7. 求叶节点的个数。

接下来我们来看一下各个模块实现的细节以及详解。

3、2、1 定义结构体

   定义结构体时,由上面的链式存储结构我们直到该结构体应该包含一个存储数据的变量,和指向左右分支节点的指针;我们看代码实现。

typedef char BTDataType;
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	BTDataType data;
}BTNode;

3、2、2 自定义一个二叉树

  首先我们自己要有一个二叉树,简单的二叉树即可。因为下面的操作都是在二叉树上进行的。这里给出一个简单的二叉树,如下图及代码实现:

	BTNode* A = (BTNode*)malloc(sizeof(BTNode));
	A->data = 'A';
	A->left = NULL;
	A->right = NULL;

	BTNode* B = (BTNode*)malloc(sizeof(BTNode));
	B->data = 'B';
	B->left = NULL;
	B->right = NULL;

	BTNode* C = (BTNode*)malloc(sizeof(BTNode));
	C->data = 'C';
	C->left = NULL;
	C->right = NULL;

	BTNode* D = (BTNode*)malloc(sizeof(BTNode));
	D->data = 'D';
	D->left = NULL;
	D->right = NULL;

	BTNode* E = (BTNode*)malloc(sizeof(BTNode));
	E->data = 'E';
	E->left = NULL;
	E->right = NULL;
	
	A->left = B;
	A->right = C;
	B->left = D;
	B->right = E;

3、2、3 前序遍历

  什么是前序遍历呢?我们先来看一下比较官方的解释。

  NLR :前序遍历(Preorder Traversal 亦称先序遍历 )—— 访问根结点的操作发生在遍历其左右子树之前。

   我在稍微解释一下前序遍历的概念:其实就是遍历树时,先访问根,再访问左子树,最后访问右子树。这里要注意的是,当我们访问到左子树时,我们把左子树当成一个新树,同时也应该满足先访问根,在访问左子树,最后访问右子树。我们发现前序遍历先访问了整个树的跟后,再把整个树左子树访问完后,再从下往上依次访问整个数的右子树。

   其实我们不难发现,当一个子树的节点为空时,我们就不再往下访问了,开始从下往上访问右子树。这好像与递归有点类似哦!其实就是用递归实现的遍历。我们结合着下图理解一下:

 注:

  • 往下的箭头表示递归调用;
  • 往上的箭头表示返回,也就是归。

下面我们看代码的实现。

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

3、2、4 中序遍历

  什么是中序遍历呢?同样,我们先来看一下比较官方的解释。

   LNR:中序遍历 (Inorder Traversal)—— 访问根结点的操作发生在遍历其左右子树的中 间。
  通俗来讲,其实就是遍历树时, 先访问左子树,再访问根,最后访问右子树。对比前序遍历,中序遍历与前序遍历大同小异,只不过是访问顺序发生了变化。我们结合这下图理解一下:

 注:

  • 往下的箭头表示递归调用;
  • 往上的箭头表示返回,也就是归。

下面我们看代码的实现。

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

3、2、5 后序遍历

  当我们了解完前序遍历和中序遍历后,我们理解后序遍历接很简单了。 我们先看比较官方的解释。

  LRN :后序遍历 (Postorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之后。
  通俗来讲,其实就是遍历树时, 先访问左子树,再右子树,最后访问根。我们直接看图:
注:
  • 往下的箭头表示递归调用;
  • 往上的箭头表示返回,也就是归。

下面我们看代码的实现。

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

3、2、6 求树中节点的个数

  我们在统计树中节点的个数时,需要遍历整个树才行。当然,遍历整个树也是需要用递归的。当我们遇到某个节点为空时,我们就返回0,不是空时我们就返回1。我们结合着代码一起理解一下。 

int TreeNodeSize(BTNode* root)
{
	return root == NULL ? 0 : TreeNodeSize(root->left) + TreeNodeSize(root->right) + 1;
}

3、2、7 求树中叶节点的个数

  我们知道,叶节点的度为0,也就是叶节点的左子树和右子树均为空。同样,我们使用递归遍历整个树,遇到节点为空时返回零,遇到节点的左子树和右子树均为空返回1。我们看代码的实现。 

int TreeLeafSize(BTNode* root)
{
	if (root == 0)
		return 0;

	if (root->left == NULL && root->right == NULL)
		return 1;

	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

3、3 二叉树的性质

  通过上面对二叉树的理解,我给大家总结出了二叉树的一些性质:

  1. 若规定根节点的层数为 1 ,则一棵非空二叉树的 i 层上最多有 2^(i-1) 个结点;
  2. 若规定根节点的层数为 1 ,则 深度为 h 的二叉树的最大结点数是 2^h- 1;
  3. 对任何一棵二叉树 , 如果度为 0 其叶结点个数为 n0, 度为 2 的分支结点个数为 n2, 则有 n0 n2 1;
  4. 若规定根节点的层数为 1 ,具有 n 个结点的满二叉树的深度 h=LogN。

  通过上面的学习,我们可以对二叉树有一个新的认识和理解,希望本编文章对您有所帮助,感谢阅读ovo! 

有关数据结构与算法之《二叉树》详解的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  4. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  5. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  6. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  7. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  8. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  9. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

  10. ruby-on-rails - 创建 ruby​​ 数据库时惰性符号绑定(bind)失败 - 2

    我正在尝试在Rails上安装ruby​​,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf

随机推荐