“二叉树:可以点一下封面吗?”

前言

树形结构的应用场合非常多,比如计算机某块硬盘下的目录结构、一个公司的组织架构划分、一个家族的族谱等等。

在计算机领域,树形结构也被广泛应用,比如编译器、数据库里都会用到,也因此,树形结构非常重要。而在众多树形结构中,最常用的一种,就是二叉树了。

1. 树的基本概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。(如下图所示)

什么是 “非线性的数据结构” ?想象一下,一根树枝可以分叉出很多树枝、树叶,我们也可以将 “非线性的数据结构” 理解成一种一对多的关系,而不是像一条线一样,按顺序排列的一对一关系。

在这里插入图片描述

树的特征:

有一个特殊的结点,称为根结点,根结点没有前驱结点。

除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i
<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
因此,树是递归定义的。

注意,如果子树存在,那么子树之间不能相交,比如下图所列的情形就不是一棵树,节点 E 和 F 之间以及节点 G 和 K 之间都不应该有连线:

在这里插入图片描述

树的基本术语

在这里插入图片描述

根节点: 一棵树最上面的节点称为根节点;如图:根节点为 A。

节点的度: 一个节点含有的子树的个数称为该节点的度; 如图:A 有 B、C、D、E 四个子树,那么 A 的度为 4。

父节点: 若一个节点含有子节点,则这个节点称为其子节点的父节点; 如图:A 是 B、C、D、E 的父节点。

子节点: 一个节点含有的子树的根节点称为该节点的子节点; 如图:B 是 A 的子节点。

(父节点、子节点: 如果一个节点下面连接多个节点,那么该节点称为父节点,它下面的节点称为子节点。)

叶子节点(终端节点): 没有任何 子节点 的节点称为叶子节点,也就是 度为 0 的节点;如图:C、F、I、J、K、L、M、O 为 叶子节点。

分支节点(非终端节点): 度不为 0 的节点; 如图:B、D、E…等节点为分支节点。

兄弟节点: 具有相同父节点的节点互称为兄弟节点; 如图:B、C 是兄弟节点。

树的度: 一棵树中,最大的节点的度称为树的度,也就是该节点孩子的个数; 如图:A 的度为 4,B 的度为 3,C 的度为 0,D 的度为 2,E 的度为 1 等等,那么 树的度 为 树中拥有最多节点的度,所以 树的度 为 4。

节点的层次: 从根开始定义起,根为第 1 层,根的子节点为第 2 层,以此类推;如图:树总共有 5 层。

树的高度(深度): 树中节点的最大层次; 如图:树中最大的层次为 5,那么树的高度就为 5。

堂兄弟节点: 双亲在同一层的节点互为堂兄弟;如图:D、E 的父亲都在同一层,那么 D、E 就为堂兄弟节点。

节点的祖先: 从根到该节点所经分支上的所有节点;如图:A 是所有节点的祖先。

子孙: 以某节点为根的子树中任一节点都称为该节点的子孙。如图:所有节点都是 A 的子孙

森林: 由 m(m>0)棵互不相交的树的集合称为森林。

树的表示

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。

我们这里就简单的了解其中最常用的孩子兄弟表示法。

typedef int DataType;
struct Node
{
	struct Node* firstChild1; // 第一个孩子结点
	struct Node* pNextBrother; // 指向其下一个兄弟结点
	DataType data; // 结点中的数据域
}

对于任意树,我们都可以用孩子兄弟法访问到树中的每一个结点(如下图所示)。

在这里插入图片描述

树在实际中的运用(表示文件系统的目录树结构)

在这里插入图片描述

2.二叉树的基本概念

⼆叉树(Binary Tree)是树的⼀种特殊形式。⼆叉,顾名思义,这种树的每个节点最多有 2 个孩⼦节点 。

注意,这⾥是最多有 2 个,也可能只有 1 个,或者没有孩⼦节点。

二叉树的特点是每个节点 最多 有两棵子树(左子树 和 右子树),这意味着每个节点的度都不大于 2。它的两棵子树有左右之分。想象一下,人的脚是分左右的,右脚不能穿左侧的鞋,和二叉树两棵子树的左右之分是一个道理。另外,次序也是不能随意颠倒的,这表明二叉树是一棵有序树。

这里,我们给二叉树下一个明确的定义:二叉树是 n(n≥0)个节点的有限集合,该集合或者为空集(即 n=0,叫做空二叉树),或者由一个根节点和两个互不相交的该根节点的左子树和右子树构成,左子树和右子树又分别是一棵二叉树。

此外,⼆叉树还有两种特殊形式,⼀个叫作 满⼆叉树 ,另⼀个叫作 完全⼆叉树 。

特殊二叉树

满二叉树

⼀个⼆叉树的所有⾮叶⼦节点都存在左右孩⼦,并且所有叶⼦节点都在同⼀层级上,那么这个树就是满⼆叉树。

在这里插入图片描述
简单点说,满⼆叉树的每⼀个分⽀都是满的。

可以看到,满二叉树看上去是很平衡的。在同样高度的二叉树当中,满二叉树一定是节点个数最多,叶子数最多的二叉树。

那要怎么去定义 “满二叉树” 呢?

观察一下,图中,第一层有 1 个节点,第二层有 2 个节点,第三层有 4 个节点,第四层有 8 个节点,所以总共的节点数为 1+2+4+8=15 个,即 2^4-1 个。

由此,我们可以给出满二叉树的定义:满二叉树是指一棵高度为 h,且含有 2^h - 1 个节点的二叉树。

最后,我们说一下编号的特点。

按照图中的顺序给每个满二叉树的节点从上到下从左到右进行编号,比如图中的 1 到 15,不难发现,编号为 i 的分支节点,它的左孩子的编号为 2i,右孩子的编号为 2i+1。如果节点 i 存在父节点,则其父节点的编号是 i/2 的结果,如果没整除,那么去掉小数部分即可。

完全二叉树

完全二叉树理解起来有一点难度。下图就是一棵完全二叉树:
在这里插入图片描述

完全二叉树有什么特点呢?

1.叶节点都在最底下两层。
2.最后一层的叶节点都靠左侧排列(左侧连续),并且除最后一层,其他层的节点个数都要达到最大。
3.倒数第二层如果有叶节点,则叶节点都靠右侧排列(右侧连续)。
4.如果节点度为 1,则该节点只有左子树,不可以只有右子树。而且最多只有一个度为 1 的节点。可以看到,满二叉树一定是一棵完全二叉树,但完全二叉树不一定是满二叉树。

可以看到,满二叉树一定是一棵完全二叉树,但完全二叉树不一定是满二叉树。

现在,我们可以借助满二叉树的概念,给出完全二叉树的定义:一棵高度为 h 的完全二叉树,当且仅当其每个节点都与高度为 h 的满二叉树中编号为 1~n 的节点一 一对应时,称为完全二叉树。

按照图中的顺序,我们给每个完全二叉树的节点从上到下从左到右进行编号,不难发现,编号为 i 的分支节点,其左孩子的编号为 2i,右孩子的编号为 2i+1。如果节点 i 存在父节点,则其父节点的编号是 i/2 的结果,如果没整除则去掉小数部分。

下图所示的几棵二叉树就都不是完全二叉树:

在这里插入图片描述
图中第一棵树的节点 5 缺少左子树(编号 10),第二棵树的节点 3 缺少两棵子树(编号 6、7),第三棵树的节点 5 缺少两棵子树(编号 10、11)。

所以,如何判断是否是完全二叉树,就可以按照下面的步骤去做:

1.在看到一棵树后,按照满二叉树的情形给该二叉树的节点进行逐层按顺序编号,如果编号出现了空缺,就不是完全二叉树,否则就是完全二叉树。

2.一棵满二叉树,依次把编号最大的 1 到多个节点去掉(比如去掉上图中的 15、14、13 节点),得到的就是一棵完全二叉树。

3.如果一个完全二叉树有 n 个节点,那么当 “节点的编号” ≤ (n/2) 时,这些节点就是分支节点,而当 “节点的编号” > (n/2) 时,这些节点就是叶节点。注意,n/2 如果没有整除则去掉小数部分。

二叉树的性质

性质 1:

若规定根节点的层数为 1,则一棵非空二叉树的第 i 层上最多有 2 ^ ( i - 1)个结点。

性质 2:

若规定根结点的层数为1,则深度为h的二叉树的最大结点数是 2 ^ h - 1.

性质 3:

对任何一棵二叉树,如果度为 0 其叶结点个数为 n0,度为 2 的分支结点个数为 n 2 ,则有 n 0 = n 2 + 1

性质 4:

若规定根节点的层数为 1,具有 n 个结点的满二叉树的深度,h = l o g 2 n + 1 h=log_{2}^{n+1}h=log
2
n+1

性质 5:

对于具有 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 否则无右孩子。

二叉树的存储结构

⼆叉树在内存中是怎样存储的呢?

我们知道数据结构可以划分为 物理结构 和 逻辑结构。⼆叉树属于逻辑结构,它可以通过多种物理结构来表达。

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

顺序存储

顺序结构就是使用 数组 来存储(如下图所示)
在这里插入图片描述

使⽤数组存储时,会按照层级顺序把⼆叉树的节点放到数组中对应的位置上。如果某⼀个节点的左孩⼦或右孩⼦空缺,则数组的相应位置也空出来。

为什么这样设计呢?因为这样可以更⽅便地在数组中定位⼆叉树的孩⼦节点和⽗节点。

假设⼀个⽗节点的下标是 parent
那么它的左孩⼦节点下标就是 leftChild = 2×parent + 1;
右孩⼦节点下标就是 rightChild = 2×parent + 2。
反过来,假设⼀个孩⼦节点的下标是 child(不分左右孩子),那么它的⽗节点下标就是parents=(child−1)/2 。

假如节点 4 在数组中的下标是 3,节点 4 是节点 2 的左孩⼦,节点 2 的下标可以直接通过计算得出。

节点 2 的下标 = (3-1) / 2 = 1

显然,对于⼀个稀疏的⼆叉树来说,⽤数组表⽰法是⾮常浪费空间的。

什么样的⼆叉树最适合⽤数组表⽰呢?我们后⾯即将学到的 ⼆叉堆,⼀种特殊的完全⼆叉树,就是⽤数组来存储的。

链式存储

链式存储是⼆叉树最直观的存储⽅式。

之前讲过链表,链表是⼀对⼀的存储⽅式,每⼀个链表节点拥有 data 变量和⼀个指向下⼀节点的 next 指针。

⼆叉树稍微复杂⼀些,⼀个节点最多可以指向 左右两个孩⼦节点,所以⼆叉树的每⼀个节点包含 3 部分 :

1)存储数据的 data 变量
2)指向左孩⼦的 left 指针
3)指向右孩⼦的 right 指针

在这里插入图片描述
在这里插入图片描述
代码示例

typedef int BTDataType;

// 二叉链
struct BinaryTreeNode
{
	struct BinTreeNode* _pLeft; // 指向当前节点左孩子
	struct BinTreeNode* _pRight; // 指向当前节点右孩子
	BTDataType _data; // 当前节点值域
}

// 三叉链
struct BinaryTreeNode
{
	struct BinTreeNode* _pParent; // 指向当前节点的双亲
	struct BinTreeNode* _pLeft; // 指向当前节点左孩子
	struct BinTreeNode* _pRight; // 指向当前节点右孩子
	BTDataType _data; // 当前节点值域
}

二叉树练习题

题目 1

某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为?

选项

A、不存在这样的二叉树
B、200
C、198
D、199

解析

答案:B
根据 性质 3,叶子结点(度为0)的个数 200 个,由于 199 + 200 = 399(该二叉树的总结点数),所以该二叉树的叶子结点数为 200。
这道题很简单,直接把选项带入题目中去

题目 2

下列数据结构中,不适合采用顺序存储结构的是?

选项

A、非完全二叉树
B、堆
C、队列
D、栈

解析

答案:C
队列 可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

题目 3

在具有 2n 个结点的完全二叉树中,叶子结点个数为?

选项

A、n
B、n+1
C、n-1
D、n/2

解析

答案:A
根据 性质 3,度为 0 的结点数和度为 2 的结点数之和应为 奇数,因为该完全二叉树的结点总数为 2n(偶数),所以二叉树中必然存在一个度为 1 的结点。
于是可以推出:度为 0 的结点和度为 2 的结点总共有 2n-1 个。
性质 3:对任何一棵二叉树,度为 0 的叶结点个数比度为 2 的分支结点个数多 1,所以该二叉树度为 1 的结点个数为 n-1,度为 0 的结点数(即叶结点数)为 n。

难点注意
任何一棵 完全二叉树 中度为 1 的结点要么有 1 个,要么就没有度为 1 的结点。

因为完全二叉树的最后一层的结点必须是从左到右连续的,而位于最后一层之前的层数的结点的度均为 2

题目 4

一棵完全二叉树的节点数位为 531 个,那么这棵树的高度为?

选项

A、11
B、10
C、8
D、n/2

解析

答案:B
假设该完全二叉树的层数为 k,则该完全二叉树的前 k - 1 层的结点总数为 2 ^( k − 1) - 1
若该完全二叉树是 满二叉树,则该满二叉树的结点总数为 2^k - 1
所以深度为 k 的完全二叉树的结点总数范围为:2^ (k − 1) − 1 < N < = 2 ^ k − 1
因为 2 ^ 9 < 531 < = 2 ^10 ,所以该完全二叉树的高度为 10。
注意:2^ 10 = 1024

题目 5

一个具有 767 个节点的完全二叉树,其 叶子节点 个数为?

选项

A、383
B、384
C、385
D、386

解析

答案:B
该题与第 3 题的道理是一样的,因为该树的结点总数为 767(奇数),所以该树中不存在度为 1 的结点;
度为 2 的结点个数为 383,度为 0 的结点个数为 384,
即叶子结点个数为 384

题目 6

下列关键字序列为堆的是:()
A 100,60,70,50,32,65
B 60,70,65,50,32,100
C 65,100,70,32,50,60
D 70,65,100,32,50,60
E 32,50,100,70,65,60
F 50,100,70,65,60,32

答案

A

题目7

已知小根堆为8,15,10,21,34,16,12,删除关键字 8 之后需重建堆,在此过程中,关键字之间的比较次
数是()。
A 1
B 2
C 3
D 4

答案

C

题目 8

一组记录排序码为(5 11 7 2 3 17),则利用堆排序方法建立的初始堆为
A(11 5 7 2 3 17)
B(11 5 7 2 17 3)
C(17 11 7 2 3 5)
D(17 11 7 5 3 2)
E(17 7 11 3 5 2)
F(17 7 11 3 2 5)

答案

C

题目9

最小堆[0,3,2,5,7,4,6,8],在删除堆顶元素0之后,其结果是()
A[3,2,5,7,4,6,8]
B[2,3,5,7,4,6,8]
C[2,3,4,5,7,8,6]
D[2,3,4,5,6,7,8]

答案

C

  • 42
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Filex;

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值