C/C++数据结构(七) —— Tree的前世今生

在这里插入图片描述

文章目录

  • 1. 树
    • 什么是树
    • 树的基本术语
    • 树的表示
    • 树在实际中的运用
  • 2. 二叉树
    • 什么是二叉树
    • 满二叉树
    • 完全二叉树
    • 二叉树的性质
    • 二叉树的存储结构
      • 顺序存储
      • 链式存储
  • 3. 二叉树练习题
    • 题目 1
    • 题目 2
    • 题目 3
    • 题目 4
    • 题目 5


1. 树

什么是树

什么是 呢?在现实⽣活中有很多体现 的逻辑的例⼦。

例如企业⾥的职级关系,就是⼀个 “树”如下图所示)。C/C++数据结构(七) —— Tree的前世今生_第1张图片

除⼈与⼈之间的关系之外,许多抽象的东⻄也可以成为⼀个 “树”,如⼀本书的⽬录(如下图所示)。
C/C++数据结构(七) —— Tree的前世今生_第2张图片

以上这些例⼦有什么共同点呢?为什么可以称它们为 “树” 呢?

因为它们都像⾃然界中的树⼀样,从同⼀个 衍⽣出许多 枝⼲,再从每⼀个 枝⼲ 衍⽣出许多更⼩的 枝⼲,最后衍⽣出更多的 叶⼦如下图所示)。

C/C++数据结构(七) —— Tree的前世今生_第3张图片

在数据结构中,树是一种 非线性 的数据结构,它是由 nn>=0)个有限结点组成一个具有层次关系的集合。

把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。(如下图所示)
C/C++数据结构(七) —— Tree的前世今生_第4张图片

树的特点:

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

树的基本术语

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

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

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

子节点: 一个节点含有的子树的根节点称为该节点的子节点; 如下图:BA 的子节点。

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

叶子节点(终端节点): 没有任何 子节点 的节点称为叶子节点,也就是 度为 0 的节点;如下图:CFIJKLMO叶子节点

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

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

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

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

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

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

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

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

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

树的示例

C/C++数据结构(七) —— Tree的前世今生_第5张图片

树的表示

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间
的关系。

实际中树有很多种表示方式如:双亲表示法孩子表示法孩子双亲表示法 以及 孩子兄弟表示法 等。

这里就简单的举例,孩子兄弟表示法。

代码示例

typedef int DataType

struct Node
{
	struct Node* firstChild;   //第一个孩子结点
	struct Node* nextBrother;  //指向下一个兄弟结点
	DataType data;             //结点中的数据域
};

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

C/C++数据结构(七) —— Tree的前世今生_第6张图片

树在实际中的运用

Linux 中,表示文件系统的目录树结构(如下图所示)。

C/C++数据结构(七) —— Tree的前世今生_第7张图片

2. 二叉树

什么是二叉树

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

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

⼆叉树的结构如图所⽰

C/C++数据结构(七) —— Tree的前世今生_第8张图片

⼆叉树节点的两个孩⼦节点,⼀个被称为 左孩⼦left child) ,⼀个被称为 右孩⼦right child) 。这两个孩⼦节点的顺序是固定的,就像⼈的左⼿就是左⼿,右⼿就是右⼿,不能够颠倒或混淆。

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

满二叉树

什么是满⼆叉树呢?

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

C/C++数据结构(七) —— Tree的前世今生_第9张图片

简单点说,满⼆叉树的每⼀个分⽀都是满的。

性质:

1)一颗树深度为 h,最大层数为 k,深度与最大层数相同,k = h
 
2)叶子节点数(最后一层)为: 2 k − 1 2^k−1 2k1
 
3)第 i 层的节点数是: 2 i − 1 2^i−1 2i1
 
4)总节点数是: 2 k − 1 2^k-1 2k1,且总节点数一定是 奇数

完全二叉树

什么⼜是完全⼆叉树呢?完全⼆叉树的定义很有意思。

对⼀个有 n 个节点的⼆叉树,按层级顺序编号,则所有节点的编号为从 1n

如果这个树所有节点和同样深度的满⼆叉树的编号为从 1n 的节点位置相同,则这个⼆叉树为完全⼆叉树。

这个定义还真绕,看看下图就很容易理解了(如下图所示)。

C/C++数据结构(七) —— Tree的前世今生_第10张图片

在上图中,⼆叉树编号从 11212 个节点,和前⾯满⼆叉树编号从 112 的节点位置完全对应。因此这个树是完全⼆叉树。

完全⼆叉树的条件没有满⼆叉树那么苛刻:

满⼆叉树要求所有分⽀都是满的;
 
⽽完全⼆叉树只需保证最后⼀个节点之前的节点都⻬全即可。

性质:

若设二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。满足下列性质:
 
1)只允许最后一层有空缺结点且空缺在右边,即叶子节点只能在层次最大的两层上出现;
 
2)对任一节点,如果其右子树的深度为 j,则其左子树的深度必为 jj+1。 即度为 1 的点只有 1 个或 0 个;
 
3)除最后一层,第 i 层的节点数是: 2 i − 1 2^i−1 2i1
 
4)有 n 个节点的完全二叉树,其深度为: l o g 2 n + 1 log_{2}^{n+1} log2n+1 l o g 2 n + 1 log_{2}^{n+1} log2n+1log2 为底,n+1 为对数);
 
5)满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。

二叉树的性质

性质 1:

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

性质 2:

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

性质 3:

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

性质 4:

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

性质 5:

对于具有 n 个结点的完全二叉树,如果按照 从上至下 从左至右 的数组顺序对所有节点从 0 开始编号,则对于序号为 i 的结点有:
 
1) 若 i > 0i 位置节点的双亲序号:(i-1) / 2i = 0i 为根节点编号,无双亲节点。
 
2) 若 2i + 1 < n,左孩子序号:2i + 12i + 1 >= n 否则无左孩子。
 
3)若 2i + 2 < n,右孩子序号:2i + 22i + 2 >= n 否则无右孩子。

如下图所示

C/C++数据结构(七) —— Tree的前世今生_第11张图片

二叉树的存储结构

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

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

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

顺序存储

顺序结构就是使用 数组 来存储(如下图所示

C/C++数据结构(七) —— Tree的前世今生_第12张图片

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

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

假设⼀个⽗节点的下标是 parent
 
那么它的左孩⼦节点下标就是 l e f t C h i l d = 2 × p a r e n t + 1 leftChild = 2×parent + 1 leftChild=2×parent+1
 
右孩⼦节点下标就是 r i g h t C h i l d = 2 × p a r e n t + 2 rightChild = 2×parent + 2 rightChild=2×parent+2
 
反过来,假设⼀个孩⼦节点的下标是 child(不分左右孩子),那么它的⽗节点下标就是 p a r e n t s = ( c h i l d − 1 ) / 2 parents = (child-1)/ 2 parents=(child1/2

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

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

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

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

链式存储

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

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

C/C++数据结构(七) —— Tree的前世今生_第13张图片

⽽⼆叉树稍微复杂⼀些,⼀个节点最多可以指向 左右两个孩⼦节点,所以⼆叉树的每⼀个节点包含 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; // 当前节点值域
}

3. 二叉树练习题

既然我们已经了解二叉树的基本概念,来看几道选择题吧!

题目 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
 
如下图所示
 C/C++数据结构(七) —— Tree的前世今生_第14张图片

题目 4

题目描述

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

选项

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

答案解析

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

题目 5

题目描述

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

选项

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

答案解析

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

你可能感兴趣的:(数据结构艺术,数据结构,算法,二叉树,霍夫曼树)