第5章树和二叉树 前面介绍了几种常见的线性结构,本章介绍的树和二叉树与第6章介绍的 图属于非线性数据结构。线性结构中的每个元素有唯一的前驱元素和唯一的后 继元素,而非线性结构中元素间前驱和后继的关系并不具有唯一性。其中,树中 结点间的关系是前驱结点唯一而后继结点不唯一,即结点间是一对多的关系;图 中结点的前驱结点和后继结点都不唯一,即结点间是多对多的关系。树的应用 非常广泛,特别是在需要进行大量数据处理的时候,如在文件系统、编译系统、目 录组织等方面,显得更加突出。 本章学习重难点: 第 5 章 树和二叉树 5.树 1 树是一种非线性的数据结构,树中的元素之间具有一对多的层次关系。 5.1.1 树的定义 树( e)是n(个结点的有限集合。其中,当n=0时,称为空树;当n>0时,称为 非空树。树满足以下条件: trn≥0) (1)有且只有一个称为根(t)的结点。 roo (2)当n>1时,其余n-1个结点可以划分为 m 个有限集合T1,T2,…,Tm ,且这 m 个 有限集合互不相交,其中Ti(1≤i≤m)又是一棵树,称为根的子树。 图5-1给出了一棵树的逻辑结构,它像一棵倒立的树。 图5- 1 树的逻辑结构 在图5-1中, A 为根结点。左边的树只有根结点。右边的树有14个结点,除了根结点, 其余的13个结点分为3个不相交的子集:T1={B,E, K ,L}、C, H , M , F,T2={G,I, N } 和T3={D,J}。其中,T1、T2 和T3 是根结点 A 的子树,并且它们本身也是一棵树。例 如,T2 的根结点是C,其余的5个结点又分为3个不相交的子集:T21={G, M }、T22= {和T23={ N }。T21 、T22和T23是T2 的子树。 G 是T21的根结点,{是 G 的子树; H } I, M } I 是T23的根结点,{ N }是 I 的子树。 表5-1是树的基本概念。 表5- 1 树的基本概念 概念定义举例 树的结点包含一个数据元素及若干指向子树分支的信息A、B、 C 等都是结点 结点的度一个结点拥有子树的个数结点 C 有3个子树,度为3 叶子结点也称为终端结点,是没有子树的结点,它的度为0 K 、L、F、 M 、 H 、 N 和 J 都是叶子结点 分支结点也称为非终端结点,是度不为0的结点B、C、D、 E 等都是分支结点 孩子结点一个结点的子树的根结点 B 是 A 的孩子结点, E 是 B 的孩子结 点, H 是 C 的孩子结点 双亲结点 也称父结点,如果一个结点存在孩子结点,则该 结点就称为孩子结点的双亲结点 A 是 B 的双亲结点, B 是 E 的双亲结 点, I 是 N 的双亲结点 139 数据结构(Python 语言描述) 续表 概念定义举例 子孙结点 在一个根结点的子树中的任何一个结点都称为 该根结点的子孙结点 {G,H,I,M,N}是 C 的子树,子树中的结 点G、H、I、 M 和 N 都是 C 的子孙结点 祖先结点 从根结点开始到达一个结点经过的所有分支结 点都称为该结点的祖先结点 N 的祖先结点为A、 C 和 I 兄弟结点 一个双亲结点的所有孩子结点之间互为兄弟 结点 E 和 F 是 B 的孩子结点,因此, E 和 F 互为兄弟结点 树的度树中所有结点的度的最大值 结点 C 的度为3,结点 A 的度为3,这两 个结点的度是树中度最大的结点,因此 树的度为3 结点的层次 从根结点开始,根结点为第一层,根结点的孩子 结点为第二层,以此类推,如果某一个结点是第 L 层,则其孩子结点位于第L+1 层 在图5-1所示的树中, A 的层次为1, B 的层次为2, G 的层次为3, M 的层次 为4 树的深度也称为树的高度,树中所有结点的层次最大值图5-1所示的树的深度为4 有序树如果树中各个子树有先后次序,则称之为有序树 无序树如果树中各个子树没有先后次序,则称之为无序树 森林 m 棵互不相交的树构成一个森林。如果把一棵非 空树的根结点删除,则该树就变成了一个森林,森 林中的树由原来的根结点和各个子树构成。如果 给一个森林加上一个根结点,将该森林中的树变 成该根结点的子树,则该森林就转换成一棵树 5.1.2 树的逻辑表示 树的逻辑表示可分为4种:树形表示法、文氏图表示法、广义表表示法和凹入表示法。 (1)树形表示法。图5-1就是树形表示法。树形表示法是最常用的一种逻辑表示,它 能直观、形象地表示出树的逻辑结构,能够清晰地反映出树中结点之间的逻辑关系。树中的 结点使用圆圈表示,结点间的关系使用直线表示,位于直线上方的结点是双亲结点,位于直 线下方的结点是孩子结点。 (2)文氏图表示法。文氏图是利用数学中的集合描述树的逻辑关系的图。图5-1的树 采用文氏图表示如图5-2所示。 (3)广义表表示法。可以采用广义表的形式表示树的逻辑结构,广义表的子表表示结 点的子树。图5-1的树利用广义表表示如下所示: (A(E(L),C( M ),I(D( B( K ,F),G( H , N )),J))) (4)凹入表示法。图5-1的树采用凹入表示法如图5-3所示 。 在这4种树的表示法中,树形表示法最为常用 。 5.1.3 树的抽象数据类型 树的抽象数据类型定义了树中的数据对象、数据关系及基本操作。树的抽象数据类型 描述如表5-2所示。 140 第 5 章 树和二叉树 图5- 2 树的文氏图表示法图5- 3 树的凹入表示法 表5- 2 树的抽象数据类型描述 数据对象 D 是具有相同特性的数据元素的集合 数据关系 若 D 为空集,则称为空树。若 D 仅含一个数据元素,则 R 为空集,否则R={ H }, H 是如下 二元关系: (1)在 D 中存在唯一的称为根的数据元素root,它在关系 H 下无前驱结点。 (2)若D-{root}≠.,则存在D-{root}的一个划分D1,D2,…,Dm (m>0),对任意的j≠ k (1≤j,k≤m)有Dj ∩Dk =.,且对任意的i(1≤i≤m),唯一存在数据元素xi ∈Di ,有< root,xi >∈ H 。 (3)对应于D-{root}的划分, H -{<root,x1>},<root,x2>,…,<root,xn >}有唯一的 一个划分H1,H2,…,Hm (m>0),对任意的j≠k(1≤j,k≤m)有Dj ∩Dk =.,且对任意的 i(1≤i≤m),Hi 是Di 上的二元关系,(Di ,{Hi})是一棵符合本定义的树,称为root的子树 InitTre(&T) 初始条件:树 T 不存在。 操作结果:构造空树 T DestroyTre(&T) 初始条件:树 T 存在。 操作结果:销毁树 T CreateTre(&T) 初始条件:树 T 不存在。 操作结果:根据给定条件构造树 T TreEmpty(T) 初始条件:树 T 存在。 操作结果:若树 T 为空树,则返回True;否则返回False Root(T) 初始条件:树 T 存在。 操作结果:若树 T 非空,则返回树的根结点;否则返回None 基本操作Parent(T,e) 初始条件:树 T 存在, e 是 T 中的某个结点。 操作结果:若 e 不是根结点,则返回该结点的双亲;否则返回None FirstChild(T,e) 初始条件:树 T 存在, e 是 T 中的某个结点。 操作结果:若 e 是树 T 的非叶子结点,则返回该结点的第一个孩子结 点;否则返回None NextSibling(T,e) 初始条件:树 T 存在, e 是 T 中的某个结点。 操作结果:若 e 不是其双亲结点的最后一个孩子结点,则返回它的下 一个兄弟结点;否则返回None InsertChild(&T,p,i, Child) 初始条件:树 T 存在,p指向 T 中的某个结点,非空树Child与 T 不 相交。 操作结果:将非空树Child插入到 T 中,使Child成为p指向的结点 的第 i 棵子树 141 数据结构(Python 语言描述) 续表 基本操作 DeleteChild(&T,p,i) 初始条件:树 T 存在,p指向 T 中的某个结点,1≤i≤d, d 为p所指 向结点的度。 操作结果:将p所指向的结点的第 i 棵子树删除。如果删除成功,返 回True,否则返回False TraverseTre(T) 初始条件:树 T 存在。 操作结果:按照某种次序对 T 的每个结点访问且仅访问一次 TreDepth(T) 初始条件:树 T 存在。 操作结果:若树 T 非空,返回树的深度;否则返回0 5.二叉树 2 在深入学习树之前,先介绍一种比较简单的树———二叉树。 5.2.1 二叉树的定义 二叉树(binarytre)是另一种树结构,它的特点是每个结点最多只有两棵子树。在二 叉树中,每个结点的度只可能是0、1和2。每个结点的孩子结点有左右之分,位于左边的孩 子结点称为左孩子结点或左孩子,位于右边的孩子结点称为右孩子结点或右孩子。如果 n=0,则称该二叉树为空二叉树。 下面给出二叉树的5种基本形态,如图5-4所示。 图5-4二叉树的5种基本形态 一个由12个结点构成的二叉树如图5-5所示。F是C的左孩子结点,G是C的右孩 子结点, L 是 G 的右孩子结点, G 的左孩子结点不存在。 对于深度为 k 的二叉树,若结点数为2k -1,即除了叶子 结点外,其他结点都有两个孩子结点,这样的二叉树称为满 二叉树。在满二叉树中,每一层的结点都具有最大的结点个 数,每个结点的度或者为2,或者为0(即叶子结点),不存在度 为1的结点。从根结点出发,从上到下,从左到右,依次对每 个结点进行连续编号,一棵深度为4的满二叉树及其结点编 号如图5-6所示。 如果一棵二叉树有 n 个结点,并且这 n 个结点的结构与满二叉树的前 n 个结点的结构 完全相同,则称这样的二叉树为完全二叉树。完全二叉树及其结点编号如图5-7所示。而 图5-5一棵二叉树 142 第 5 章 树和二叉树 图5-8所示就不是一棵完全二叉树。 图5- 6 一棵深度为4的满二叉树及其结点编号 图5- 7 一棵完全二叉树及其结点编号 图5- 8 一棵非完全二叉树 由此可以看出,如果二叉树的层数为k,则满二叉树的叶子结点一定在第 k 层,而完全 二叉树的叶子结点一定在第 k 层或者第k-1层。满二叉树一定是完全二叉树,而完全二叉 树却不一定是满二叉树。 5.2.2 二叉树的性质 二叉树具有以下重要的性质。 性质 1 在二叉树中,第m(m≥1)层上至多有2m-1个结点(规定根结点为第一层)。 证明:利用数学归纳法证明。 当m=1时,即根结点所在的层次,有2m-1=21-1=20=1,命题成立。 假设当m= k 时命题成立,即第 k 层至多有2k-1个结点。因为在二叉树中每个结点的 度最大为2,则在第k+1 层,结点的个数最多是第 k 层的2倍,即2×2k-1=2k-1+1=2k 。因 此,当m=k+1 时,命题成立。 性质 2 深度为k(的二叉树至多有2k k≥1) 1个结点。 143 数据结构(Python 语言描述) 证明:第 i 层结点的最多个数2i-1,将深度为 k 的二叉树中每一层结点的最大值相加, 就得到二叉树中结点的最大值,因此深度为 k 的二叉树的结点总数至多为 k Σ(k) (第 i 层的结点最大个数)=Σ(k) 2i-1=20+21+ …+2k-1= 20(2-1)=2k-1 i=1i=12-1 命题成立。 性质 3 对任何一棵二叉树T,如果叶子结点总数为n0,度为2的结点总数为n2,则有 n0=n2+1 。 证明:假设在二叉树中,结点总数为n,度为1的结点总数为n1。二叉树中结点的总数 n 等于度为0、度为1和度为2的结点总数的和,即n=n0+n1+n2。 假设二叉树的分支数为Y。在二叉树中,除了根结点外,每个结点都存在一个进入的分 支,所以有n=Y+1 。 又因为二叉树的所有分支都是由度为1和度为2的结点发出,所以分支数Y=n1+ 2n2。故n=Y+1=n1+2n2+1 。 联合n=n0+n1+n2 和n=n1+2n2+1 两式,得到n0+n1+n2=n1+2n2+1,即n0= n2+1 。命题成立。 性质 4 如果完全二叉树有 n 个结点,则深度为 +1 。符号 表示不大于 x 的最大整数,而 x 表示不小于 x 的最小整数。 log2n 证明:假设具有 n 个结点的完全二叉树的深度为k。 k 层完全二叉树的结点个数介于 k-k x 1层满二叉树与 k 层满二叉树的结点个数之间。根据性质2,1层满二叉树的结点总 数为n1=2k-1- k 层满二叉树的结点总数为n2= k -1。因此有n1<n≤n2,即n1+1≤ 1,2n<n2+1,又n1=2k-1-1和n2=2k -1,故得到2k-1-1≤n<2k -1,同时对不等式两边取 对数,有k-1≤log2n<k。因为 k 是整数,k-1也是整数,所以k-1= log2n +1 。命题成立。 log2n ,即k= 性质 5 如果完全二叉树有 n 个结点,按照从上到下、从左到右的顺序对二叉树中的每 个结点从1到 n 进行编号,则对于任意结点 i 有以下性质: (1)如果i则序号 i 对应的结点就是根结点,该结点没有双亲结点。如果i>1,则=1, i/2。 序号为 i 的结点的双亲结点的序号为 (2)如果2i>n,则序号为 i 的结点没有左孩子结点。如果2i≤n,则序号为 i 的结点的 左孩子结点的序号为2i。 (3)如果2i+1>n,则序号为 i 的结点没有右孩子结点。如果2i+1≤n,则序号为 i 的 结点的右孩子结点序号为2i+1 。 证明 : (1)利用性质(2)和(3)证明。当i=1时,该结点一定是根结点,根结点没有双亲结点。 当i>1 时,假设序号为 m 的结点是序号为 i 的结点的双亲结点。如果序号为 i 的结点是序 号为 m 的结点的左孩子结点,则根据性质(2)有2m=i,即 m =i/2;如果序号为 i 的结点是 m+1=i-=i/2 序号为 m 的结点的右孩子结点,则根据性质(3)有2i,即 m =(1)/21/2。 综上以上两种情况,当i>1 时,序号为 i 的结点的双亲结点序号为 。结论成立。 i/2 (2)利用数学归纳法证明。当i=1时,有2i=2。如果2>n,则该二叉树中不存在序 号为2的结点,也就不存在序号为 i 的左孩子结点;如果2≤n,则该二叉树中存在两个结 144 第 5 章 树和二叉树 点,序号为2的结点是序号为 i 的结点的左孩子结点。 假设当i= k 时,如果2k≤n,序号为 k 的结点的左孩子结点存在且序号为2k;如果2k> 序号为 k 的结点的左孩子结点不存在。n, 当i=k+1时,在完全二叉树中,如果序号为k+1的结点的左孩子结点存在(2i≤n), 则其左孩子结点的序号为序号为 k 的结点的右孩子结点的序号加1,即序号为k+1的结点 的左孩子结点的序号为(2=k+1)2当2序号为 i 的结点的 左孩子不存在。结论成立 k 。 +1)+12(=i。因此, i> n 时, (3)同理,利用数学归纳法证明。当i=1时,如果2i+1=3>n,则该二叉树中不存在 序号为3的结点,即序号为 i 的结点的右孩子不存在;如果2i+1=3≤n,则该二叉树存在 序号为3的结点,且序号为3的结点是序号为 i 的结点的右孩子结点。 假设当i= k 时,如果2k+1≤n,序号为 k 的结点的右孩子结点存在且序号为2k+1, 当2k+1> n 时,序号为 k 的结点的右孩子结点不存在。 当i=k+1时,在完全二叉树中,如果序号为k+1的结点的右孩子结点存在,其序号为 2i+1≤n,则其右孩子结点的序号为序号为 k 的结点的右孩子结点的序号加2,即序号为 k+1的结点的右孩子结点的序号为(2=k+1)+12当2 k+1)+22(=i+1 。因此, i+1> n 时,序号为 i 的结点的右孩子不存在。结论成立。 5.2.3 二叉树的抽象数据类型 二叉树的抽象数据类型定义了二叉树中的数据对象、数据关系及基本操作。二叉树的 抽象数据类型描述如表5-3所示。 表5- 3 二叉树的抽象数据类型描述 数据对象 D 是具有相同特性的数据元素的集合 数据关系 若D=.,则称之为空二叉树。 若D≠.,则R={ H }, H 是如下二元关系: (1)在 D 中存在唯一的称为根的数据元素root,它在关系 H 下无前驱结点。 (2)若D-{root}≠.,则存在D-{root}={Dl,Dr},且Dl∩Dr=.。 (3)若Dl≠.,则Dl中存在唯一的元素xl,<root,xl>∈ H ,且存在Dl 上的关系Hl. H ; 若Dr≠.,则Dr 中存在唯一的元素xr,<root,xr>∈ H ,且存在Dr上的关系Hr. H ; H = {<root,xl>,<root,xr>,Hl,Hr}。 (4)(Dl,{Hl})是一棵符合本定义的二叉树,称为根的左子树;(Dr,{Hr})是一棵符合本定 义的二叉树,称为根的右子树 InitBiTre(&T) 初始条件:二叉树 T 不存在。 操作结果:构造空二叉树 T CreateBiTre(&T) 初始条件:给出了二叉树 T 的定义。 操作结果:创建一棵非空的二叉树 T 基本操作DestroyBiTre(&T) 初始条件:二叉树 T 存在。 操作结果:销毁二叉树 T InsertLeftChild(p,c) 初始条件:二叉树 c 存在且非空 c 的右子树为空 操作结果:将 c 插入到p所指向的左子树,使p所指结点的左子树 成为 c 的右子树 145 数据结构(Python 语言描述) 续表 基本操作 InsertRightChild(p,c) 初始条件:二叉树 c 存在且非空, c 的右子树为空。 操作结果:将 c 插入到p所指向的右子树,使p所指结点的右子树 成为 c 的右子树 LeftChild(&T,e) 初始条件:二叉树 T 存在, e 是 T 中的某个结点。 操作结果:若结点 e 存在左孩子结点,则将 e 的左孩子结点返回; 否则返回空 RightChild(&T,e) 初始条件:二叉树 T 存在, e 是 T 的某个结点。 操作结果:若结点 e 存在右孩子结点,则将 e 的右孩子结点返回; 否则返回空 DeleteLeftChild(&T,p) 初始条件:二叉树 T 存在,p指向 T 中的某个结点。 操作结果:将p所指向的结点的左子树删除。如果删除成功,返 回True;否则返回False DeleteRightChild(&T,p) 初始条件:二叉树 T 存在,p指向 T 中的某个结点。 操作结果:将p所指向的结点的右子树删除。如果删除成功,返 回True;否则返回False PreOrderTraverse(T) 初始条件:二叉树 T 存在。 操作结果:先序遍历二叉树T,即先访问根结点,再访问左子树, 最后访问右子树,对二叉树中的每个结点访问且仅访 问一次 InOrderTraverse(T) 初始条件:二叉树 T 存在。 操作结果:中序遍历二叉树T,即先访问左子树,再访问根结点, 最后访问右子树,对二叉树中的每个结点访问且仅访 问一次 PostOrderTraverse(T) 初始条件:二叉树 T 存在。 操作结果:后序遍历二叉树T,即先访问左子树,再访问右子树, 最后访问根结点,对二叉树中的每个结点访问且仅访 问一次 LevelTraverse(T) 初始条件:二叉树 T 存在。 操作结果:对二叉树 T 进行层次遍历,即按照从上到下、从左到右 的顺序依次对二叉树中的每个结点进行访问 BiTreDepth(T) 初始条件:二叉树 T 存在。 操作结果:若二叉树 T 非空,返回它的深度;若 T 是空二叉树,返 回0 5.2.4 二叉树的存储表示 二叉树的存储表示方式有两种:顺序存储结构和链式存储结构。 1.二叉树的顺序存储结构 我们已经知道,完全二叉树中每个结点的序号可以通过公式计算得到,因此,完全二叉 树可以按照从上到下、从左到右的顺序依次存储在一维数组或列表中。完全二叉树及其顺 序存储结构如图5-9所示。 如果按照从上到下、从左到右的顺序把非完全二叉树的结点依次存放在一维数组或列表 146 第5 章 树和二叉树 1 47 图5-9 完全二叉树及其顺序存储结构 中。为了能够正确反映二叉树中结点之间的逻辑关系,需要在一维数组(列表)中将二叉树中 不存在的结点位置空出,并用∧填充。非完全二叉树及其顺序存储结构如图5-10所示。 图5-10 非完全二叉树及其顺序存储结构 顺序存储结构对于完全二叉树来说是比较适合的,因为采用顺序存储结构能够节省存 储单元,并能够利用公式得到每个结点的存储位置。但是,对于非完全二叉树来说,这种存 储方式会浪费存储空间。在最坏的情况下,如果每个结点只有右孩子结点,而没有左孩子结 点,则需要占用2k -1个存储单元,而实际上该二叉树只有k 个结点。 2.二叉树的链式存储结构 在二叉树中,每个结点有一个双亲结点和两个孩子结点。从一棵二叉树的根结点开始, 图5-11 二叉链表的结点结构 通过结点的左右孩子地址就可以找到二叉树的每一个结点。 因此二叉树的链式存储结构包括三个域:数据域、左孩子指 针域和右孩子指针域。其中,数据域存放结点的值,左孩子 指针域指向左孩子结点,右孩子指针域指向右孩子结点。这 种链式存储结构称为二叉链表,其结点结构如图5-11所示。 二叉树的二叉链表存储表示如图5-12所示。 有时为了方便找到结点的双亲结点,在二叉链表的存储结构中增加一个指向双亲结点 的指针域parent,这种存储结构称为三叉链表,其结点结构如图5-13所示。 通常情况下,二叉树采用二叉链表表示。二叉链表存储结构的类型定义如下: class BiTreeNode(): #二叉树中的结点 def __init__(self,data,lchild=None,rchild=None): self.data=data #二叉树的结点值 self.lchild=lchild #左孩子指针 self.rchild=rchild #右孩子指针 定义了二叉树的结点后,为了实现二叉树的插入、删除、遍历和线索化,必须先创建二叉