1.07k likes | 1.27k Views
第6章 树和二叉树. 6.1 树的概念与定义 6.2 二叉树 6.3 二叉树的遍历与线索化 6.4 树、森林和二叉树的关系 6.5 哈夫曼树及其应用. 图6.1 树的图示方法. 6.1 树的概念与定义. 树是 n(n≥0) 个结点的有限集合 T。 当 n=0 时,称为空树;当 n>0 时, 该集合满足如下条件: (1) 其中必有一个称为根( root) 的特定结点,它没有直接前驱,但有零个或多个直接后继。
E N D
第6章 树和二叉树 6.1 树的概念与定义 6.2 二叉树 6.3 二叉树的遍历与线索化 6.4 树、森林和二叉树的关系 6.5 哈夫曼树及其应用
6.1 树的概念与定义 树是n(n≥0)个结点的有限集合T。当n=0时,称为空树;当n>0时, 该集合满足如下条件: (1) 其中必有一个称为根(root)的特定结点,它没有直接前驱,但有零个或多个直接后继。 (2) 其余n-1个结点可以划分成m(m≥0)个互不相交的有限集T1,T2,T3,…,Tm,其中Ti又是一棵树,称为根root的子树。 每棵子树的根结点有且仅有一个直接前驱,但有零个或多个直接后继。
树的相关概念 结点、 结点的度、 叶子、 分支结点、 孩子、兄弟、 祖先、子孙、结点的层次、堂兄弟、树的度、树的高度(深度)、有序树、森林等
A B C D E F G H I J 例1、对于下图所示的树,回答以下问题。 根: 叶子: 内部结点: A—J的度分别是: 树的度是: A的孩子: B的孩子: B的双亲: I的双亲: A C、E、G、H、I、J B、D、F 3、2、0、2、0、2、0、0、0、0 3 B、C、D E、F 树T A F
A B C D E F G H I J 例1、对于下图所示的树,回答以下问题。 互为兄弟的结点: F和G互为: A的子孙: B的子孙: I的祖先: H的祖先: B、C、D互为兄弟 E、F互为兄弟 G、H互为兄弟 I、J互为兄弟 堂兄弟 B--J E、F、I、J A、B、F 树T A、D
A B C D E F G H I J 例1、对于下图所示的树,回答以下问题。 层为1的结点: 层为2的结点: 层为3的结点: 层为4的结点: 树的深度: A B、C、D E、F、G、H I、J 4 树T
ADT Tree{ 数据对象D:一个集合,该集合中的所有元素具有相同的特性。 数据关系R: 若D为空集,则为空树。 若D中仅含有一个数据元素,则R为空集,否则R={H},H是如下的二元关系: (1) 在D中存在唯一的称为根的数据元素root, 它在关系H下没有前驱。 (2) 除root以外, D中每个结点在关系H下都有且仅有一个前驱。
基本操作: (1) InitTree(&T): 将T初始化为一棵空树。 (2) DestoryTree(&T): 销毁树T。 (3) CreateTree(&T): 创建树T。 (4) TreeEmpty(T): 若T为空, 则返回TRUE, 否则返回FALSE。 (5) Root(T): 返回树Tree的根。 (6) Parent(T, cur_e): 树T存在, cur_e是T中的某个结点。 若cur_e 为非根结点,则返回它的双亲, 否则返回“空”。 (7) LeftChild(T, cur_e ): 树T存在, cur_e是T中的某个结点。若cur_e为非叶子结点,则返回它的第一个孩子结点, 否则返回“空”。
RightSibling(T,cur_e): 树T存在,cur_e是T中的某个结点。若cur_e 有右兄弟,则返回它的右兄弟, 否则返回“空”. • InsertChild(&T, &p,i, c):树T存在,p指向T中某个结点,1≤i≤d+1,d为p所指向结点的度,非空树c与T不相交。插入c为T中p所指向结点的第i棵子树。 (10) DeleteChild(&T,&p,i): 树T存在, p指向T中某个结点, 1≤i≤d,d为p所指向结点的度。 删除T中p所指向结点的第i棵子树。 (11) TraverseTree(T,Visit()): 树T存在,Visit()是对结点操作的应用函数。按照某种次序对树T的每个结点调用Visit()函数访问一次且最多一次。若Visit()失败, 则操作失败。}
6.2 二 叉 树 • 6.2.1 二叉树的定义与基本操作 定义:我们把满足以下两个条件的树形结构叫做二叉树(Binary Tree): (1) 每个结点的度都不大于2; (2) 每个结点的孩子结点次序不能任意颠倒。
二叉树的抽象数据类型 • ADT BinaryTree{数据对象D:D是具有相同特性的数据元素的集合数据关系R:若D=Φ,则R=φ,称二叉树为空二叉树,否则有 有root,左子树DL,右子树Dr基本操作:
二叉树的五种基本形态 图6.2给出了二叉树的五种基本形态。
6.2.2 二叉树的性质 • 性质1: 在二叉树的第i层上至多有2i-1个结点(i≥1)。 证明: 用数学归纳法。 归纳基础:当i=1时,整个二叉树只有一根结点,此时2i-1=20=1,结论成立。 归纳假设:假设i=k时结论成立,即第k层上结点总数最多为2k-1个。 现证明当i=k+1时, 结论成立: 因为二叉树中每个结点的度最大为2,则第k+1层的结点总数最多为第k层上结点最大数的2倍,即2×2k-1=2(k+1)-1,故结论成立。
性质2: 深度为k的二叉树至多有2k-1个结点(k≥1)。 证明:因为深度为k的二叉树,其结点总数的最大值是将二叉树每层上结点的最大值相加,所以深度为k的二叉树的结点总数至多为 故结论成立。
性质3: 对任意一棵二叉树T,若终端结点数为n0,而其度数为2的结点数为n2,则n0=n2+1。 证明:设二叉树中结点总数为n, n1为二叉树中度为1的结点总数。因为二叉树中所有结点的度小于等于2,所以有 n=n0+n1+n2 (1) 设二叉树中分支数目为B, 因为除根结点外, 每个结点均对应一个进入它的分支,所以有n=B+1
又因为二叉树中的分支都是由度为1和度为2的结点发出, 所以分支数目为 B=n1+2n2 整理上述两式可得到 n=B+1=n1+2n2+1 (2) 将式(1)代入上式,得出n0+n1+n2=n1+2n2+1,整理后得n0=n2+1,故结论成立。
满二叉树: 深度为k且有2k-1个结点的二叉树称为满二叉树。 深度为k,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时,称之为完全二叉树。 图6.3 满二叉树与完全二叉树
性质4:具有n个结点的完全二叉树的深度为 证明:假设n个结点的完全二叉树的深度为k,根据性质2可知,k-1层满二叉树的结点总数为 n1=2k-1-1 k层满二叉树的结点总数为 n2=2k-1 显然有n1<n≤n2,进一步可以推出n1+1≤n<n2+1。 将n1=2k-1-1和n2=2k-1代入上式,可得2k-1≤n<2k, 即k-1≤log2n<k。 因为k是整数,所以k-1= ,k= , 故结论成立。
性质5: 如果对一颗有n个结点的完全二叉树(其深度为 )的结点按层序(从第1层到第 层,每层从左到右),则对任一结点i(1≤ i≤ n)有: (1)如果i=1,则结点I是二叉树的根,无双亲;如果i>1,则其双亲PARENT(i)是结点 。 (2) 如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子LCHILD(i)是结点2i。 (3) 如果2i+1>n,则结点i无右孩子;否则其右孩子RCHILD(i)是结点2i+1。
可以用归纳法证明其中的(2)和(3): 当i=1时,由完全二叉树的定义知,满足性质5:中的(2)和(3)。 1 2 3 … 4
当i>1时:情况① 第j层第1个结点,i=2j-1 i … 第j+1层第1个结点,编号为 2j=2(2j-1)=2i 第j层 … 第j+1层 2i 2i+1
当i>1时:情况② i … i+1 … … 2i 2i+1 2i+2 2i+3 …
… i i+1 … 2i 2i+1 … 2i+2 2i+3 故(2)和(3)得证。
6.2.3 二叉树的存储结构 二叉树的结构是非线性的, 每一结点最多可有两个后继。 二叉树的存储结构有两种: 顺序存储结构和链式存储结构。 1. 顺序存储结构 存储方式: 用一组地址连续的存储单元依次自上而下、自左至右存储完全二叉树上的结点元素,即将完全二叉树上编号为i的结点元素存储在一维数组中下标为i-1的分量中。 表示方式:
H A B C D G E F J I K L • #define MAX_TREE_SIZE 100 // 二叉树的最大结点数typedef TElemType SqBiTree[MAX_TREE_SIZE]; // 0号单元存储根结点SqBiTree bt; 完全二叉树 图6.4 二叉树与顺序存储结构
对于一般二叉树,则应将其每个结点与完全二叉树上的结点相对照,存储在一维数组的相应分量中。对于一般二叉树,则应将其每个结点与完全二叉树上的结点相对照,存储在一维数组的相应分量中。 图6.5 单支二叉树与其顺序存储结构
显然,这种顺序存储结构仅适用于完全二叉树。因为,在最坏的情况下,一个深度为 k 且只有 k 个结点的单支树(树中不存在度为 2 的结点)却需要长度为2k-1的一维数组。
2. 链式存储结构 1)二叉链表的定义 对于任意的二叉树来说,每个结点只有两个孩子,一个双亲结点。我们可以设计每个结点至少包括三个域:数据域、 左孩子指针域和右孩子指针域: 其中,lchild域指向该结点的左孩子,data域记录该结点的信息,rchild域指向该结点的右孩子。
用C语言可以这样声明二叉树的二叉链表结点的结构: • typedef struct BiTNode • { • TElemType data; //结点数据 • struct BiTNode *lchild; //左孩子指针 • struct BiTNode *rchild; //右孩子指针 • }BiTNode, *BiTree;
二叉链表中有几个空链域? 若一个二叉树含有n个结点,则它的二叉链表中必含有2n个指针域, 其中必有n+1个空的链域。此结论证明如下: 证明:分支数目B=n-1,即非空的链域有n-1个,故空链域有2n-(n-1)=n+1个。
表示方式:typedef struct TriTNode {TElemType data;struct TriTNode *lchild, *rchild; // 左右孩子指针struct TriTNode *parent; // 双亲指针} TriTNode, *TriTree; 2)三叉链表 有时,为了便于找到父结点,可以增加一个parent域, parent域指向该结点的双亲结点。 该结点结构如下:
采用哪种存储结构 • 考虑: 1)二叉树的形态 2)进行何种操作
练习题: 1、按照二叉树的定义,具有3个结点的二叉树有种。 A. 3 B. 4 C. 5 D. 6 答案: C 2、深度为5的二叉树至多有个结点。 A. 16 B.32 C.31 D.10 答案: C 3、对一个满二叉树,m个树叶,n个结点,深度为h,则。 A.n=h+m B.h+m=2n C.m=h-1 D.n=2h-1 答案: D
4、高度为h的完全二叉树至少有多少个结点?至多有多少个结点?4、高度为h的完全二叉树至少有多少个结点?至多有多少个结点? 解: 至少有:2h-1,至多有:2h-1
A B C E D G F H I J K L M N 5、假设在树中,结点x是结点y的双亲时,用(x,y)来表示树边。已知一棵树边的集合为:{(I,M),(I,N),(B,E),(E,I),(B,D),(A,B),(G,J),(G,K),(C,G),(C,F),(H,L),(C,H),(A,C)},用树形表示法画出此树。 解:该树为:
6、一棵度为2的树与一棵二叉树有何区别? 解: 区别(1)度为2的树至少有一个结点有二个分支,二叉树的每个结点的分支数都可以小于2; (2)度为2的树两个分支没有左右之分;二叉树的两个分支,有左右之分,且左右不能交换。
具有3个结点的树 3个结点的二叉树 7、试分别画出具有3个结点的树和3个结点的二叉树的所有不同形态。 解:
A A B C B C D E F G D E F A A B C B C D E D 8、深度为3的完全二叉树有哪几种形式?
6.3 二叉树的遍历与线索化 6.3.1遍历二叉树 二叉树的遍历是指按照某种顺序访问二叉树中的每个结点,使每个结点被访问一次且仅被访问一次。 • 遍历是二叉树中经常要用到的一种操作。因为在实际应用问题中,常常需要按一定顺序对二叉树中的每个结点逐个进行访问,查找具有某一特点的结点,然后对这些满足条件的结点进行处理。 • 通过一次完整的遍历,可使二叉树中结点信息由非线性排列变为某种意义上的线性序列。也就是说,遍历操作使非线性结构线性化。
我们用L、D、R分别表示遍历左子树、访问根结点、 遍历右子树, 那么对二叉树的遍历顺序就可以有六种方式: • (1) 访问根,遍历左子树,遍历右子树(记做DLR)。 • (2) 访问根,遍历右子树,遍历左子树(记做DRL)。 • (3) 遍历左子树,访问根,遍历右子树(记做LDR)。 • (4) 遍历左子树,遍历右子树,访问根(记做LRD)。 • (5) 遍历右子树,访问根,遍历左子树(记做RDL)。 • (6) 遍历右子树,遍历左子树,访问根(记做RLD)。
注意:先序、中序、后序遍历是递归定义的, 即在其子树中亦按上述规律进行遍历。 下面就分别介绍三种遍历方法的递归定义。 1. 先序遍历(DLR)操作过程: 若二叉树为空, 则空操作, 否则依次执行如下3个操作: (1) 访问根结点; (2) 按先序遍历左子树; (3) 按先序遍历右子树。
2 .中序遍历(LDR)操作过程: 若二叉树为空, 则空操作, 否则依次执行如下3个操作: (1) 按中序遍历左子树; (2) 访问根结点; (3) 按中序遍历右子树。 3. 后序遍历(LRD)操作过程: 若二叉树为空, 则空操作, 否则依次执行如下3个操作: (1) 按后序遍历左子树; (2) 按后序遍历右子树; (3) 访问根结点。
对于如图所示的二叉树, • 先序遍历: A、 B、 D、 F、 G、 C、 E、 H 。 • 中序遍历: B、 F、 D、 G、 A、 C、 E、 H 。 • 后序遍历: F、 G、 D、 B、 H、 E、 C、 A 。
最早提出遍历问题是对存储在计算机中的表达式求值。例如:(a+b*c)-d/e。该表达式用二叉树表示如下图所示。当我们对此二叉树进行先序、中序、后序遍历时,便可获得表达式的前缀、 中缀、 后缀书写形式: • 前缀: -+a*bc/de • 中缀: a+b*c-d/e • 后缀: abc*+de/- • 其中中缀形式是算术表达式的通常形式,只是没有括号。 前缀表达式称为波兰表达式。算术表达式的后缀表达式被称作逆波兰表达式。 在计算机内, 使用后缀表达式易于求值。