文章目录
1.树的定义
树(Tree)是 n (n >= 0)个结点的有限集。
- 若 n = 0(没有元素的数),则称为空树;
- 若 n > 0,则它满足如下两个条件:
- 有且仅有一个特定的称之为根的结点,若 n = 1,则说明这棵树只有树根。
- 除根结点以外的其余结点可分为 m (m > 0) 个不互相交的有限集 T1,T2,T3,…,Tm,其中每一个集合本身又是一棵树,并成为根的子树(SubTree)。
2. 树的基本术语
-
结点:树中的每一个独立单元。包含一个数据元素以及若干指向其子树的分支。如上图中的 A B C D 等,都是一个结点,只有根结点没有前趋。
-
结点的度:结点拥有的子树数(分支数或后继数)被称为结点的度。如:A 拥有三个子树,则A的度为3,B 的度为2,C为1,D为3,F为0。
-
树的度:树内各结点的度的最大值。如上图的树的度为3,拥有分支最多的结点是 A 和 D,都有3个结点,所以整个数的度为3。
-
叶子结点:将没有子树(分支或后继)的结点称为叶子结点(终端结点)。
-
非终端结点:度 ≠ 0 (有分支或后继)的结点,称为非终端结点或分支结点,除根结点之外,非终端结点也称为内部结点。
-
双亲和孩子:结点的子树的根称为该结点的孩子,相应的,该结点称为孩子的双亲。如:A 的孩子是 BCD,B 的双亲是 A,B 的孩子有 E 和 F。
-
兄弟:同一个双亲的孩子之间互称为兄弟。如:BCD 是兄弟,HIJ 也是兄弟,KL 也是兄弟。
-
堂兄弟:双亲位于同一层的结点互为堂兄弟。如:G 与 DFHIJ 互为堂兄弟,KLM 互为堂兄弟。
-
祖先:从根结点到该结点所经分支上的所有结点称为祖先。如:从 A 到 M 经过了 ADH,所以称 ADH 为 M 的祖先。
-
子孙:以某结点为根的子树中的任一结点称为该结点的子孙。如:B 的子孙为EFKL, D 的子孙为 HIJM,M 既是H的孩子又是H的子孙,A就不用说了。
-
层次:结点的层次从根结点开始,根结点为第一层,根结点的孩子为第二层,树中任一结点的层次为其双亲结点的层次+1。
-
树的深度:树中结点的最大层次称为树的深度或高度。如:上图的树一共有4层,所以这棵树的深度(高度)为 4。
-
有序树和无序树
- 有序树:树中结点的各个子树从左至右有次序(不能互换),在有序树中最左边的子树的根 B 称为第一个孩子,最右边的D称为最后一个孩子。
如:T1 必须是左边的子树,T2必须是中间的,T3必须是最右边的,就称为有序树,T1 T2 T3的位置如果换一下就成另外一棵树了。 - 无序树:树中结点的各个子树无次序,每个子树不管在什么位置都是这棵树。
- 森林:是 m (M >= 0)棵互不相交的数的集合。对树中每个结点而言,其子树的集合即为森林。如:上图以 A 为根结点的树也算是一个森林,只不过这个森林只有一棵树。
- 把根节点 A 删除树就变成了森林,这样就有三棵树了。
- 一棵树可以看成是一个特殊的森林。
- 给森林中的各子树加上一个双亲结点,森林就变成了树。
- 树一定是森林,只不过这个森林只有一棵树,但是森林不一定是树
2.1 树结构和线性结构的比较
线性结构
- 第一个数据元素:无前驱。
- 最后一个数据元素:无后继。
- 其他数据元素:一个前驱,一个后继。
- 元素之间关系:一对一。
树结构
- 根结点(只有一个):无双亲。
- 叶子结点(可以有多个):无孩子。
- 其他结点(中间结点):一个双亲,多个孩子。
- 元素之间关系:一对多。
3. 二叉树的定义
每个结点最多只有两个分支的树称为==二叉树。==
- 二叉树的结构最简单,规律性最强。
- 所有树都能转为唯一对应的二叉树,不失一般性。
普通树(多叉树)若不转化为二叉树,则运算很难实现
- 二叉树在树结构的应用中起着非常重要的作用,因为对二叉树的许多操作算法简单,而任何树都可以与二叉树相互转换,这样就解决了树的存储结构及其运算中存在的复杂性。
二叉树的定义
- 二叉树是 n (n >= 0) 个结点的有限集,它或者是空集(n = 0),或者由一个根结点及两棵互不相交的分别称作这个根的左子树和右子树的二叉树组成。
二叉树的特点
- 每个结点最多有两个孩子(二叉树中不存在度大于2的结点)。
- 子树有左右之分,其次序不能颠倒。
- 二叉树可以是空集合(空树),根可以有空的左子树或空的右子树。
二叉树不是树的特殊情况,他们是两个概念
- 二叉树就是二叉树。
- 二叉树结点的子树要区分左子树和右子树,即使只有一棵子树,也要进行区分,说明它是左子树,还是右子树。
- 树当结点只有一个孩子时,就无需区分它是左还是右的次序。因此二者是不同的。这是二叉树与树的最主要的差别。
二叉树的形态
- 具有两个结点的 二叉树 有两种形态,要么右子树为空,要么左子树为空。
- 而有两个结点的树只有一种状态,不区分左右子树。
- 也就是说,二叉树每个结点位置或者说次序都是固定的,可以是空,但是不能说它没有位置,而树的结点位置是相对于别的结点来说的,没有别的结点时,他就无所谓左右了。
思考一下
- 具有三个结点的二叉树可能有几种不同形态?普通树呢?
4. 树的抽象数据类型定义
4.1树的基本操作
- InitTree(&T)
- 操作结果:构造空树 T。
- DestroyTree(&T)
- 初始条件:树 T 存在。
- 操作结果:销毁树 T。
- CreateTree(&T,defintion)
- 初始条件:definition 给出树 T 的定义。
- 操作结果:按照 definition 构造树 T。
- ClearTree(&T)
- 初始条件:树 T 存在。
- 操作结果:将树 T 清空为空树。
- TreeEmpty(T)
- 初始条件:树 T 存在。
- 操作结果:若 T 为空树,则返回 true,反之返回 false
- TreeDepth(T)
- 初始条件:树 T 存在。
- 操作结果:返回 T 的深度。
- Root(T)
- 初始条件:树 T 存在。
- 操作结果:返回 T 的根。
- Value(T,cur_e)
- 初始条件:树 T 存在,cur_e 是 T 中的某个结点。
- 操作结果:返回 cur_e 的值。
- Assign(T,cur_e,value)
- 初始条件:树 T 存在,cur_e 是 T 中的某个结点。
- 操作结果:将 value 的值赋给 cur_e。
- Parent(T,cur_e)
- 初始条件:树 T 存在,cur_e 是 T 中的某个结点。
- 操作结果:若 cur_e 是 T 的非根结点,则返回它的双亲,反之返回空。
- Leftchild(T,cur_e)
- 初始条件:树 T 存在,cur_e 是 T 中的某个结点。
- 操作结果:若 cur_e 是 T 的非叶子结点,则返回它的最左孩子,反之返回空。
- RightSibling(T,cur_e)
- 初始条件:树 T 存在,cur_e 是 T 中的某个结点。
- 操作结果:若 cur_e 有右兄弟,则返回它的右兄弟,反之返回空。
- InsertChild(&T,p,i,c)
- 初始条件:树 T 存在,p 指向 T 中某个结点,1 <= i <= p 所指结点的度 +1,非空树 c 与 T 不想交。
- 操作结果:插入 c 为 T 中 p 指向结点的第 i 课子树。
- DeleteChile(&T,p,i)
- 初始条件:树 T 存在,p 指向 T 中某个结点,1 <= i <= p 指向结点的度。
- 操作结果:删除 T 中 p 所指向结点的第 i 课子树。
- TraverseTree(T)
- 初始条件:树 T 存在。
- 操作结果:安某种次序对 T 的每个结点访问一次。
4.2 二叉树的基本操作
- InitBiTree(&T)
- 操作结果:构造空二叉树 T。
- DestroyBiTree(&T)
- 初始条件:二叉树 T 存在。
- 操作结果:销毁二叉树 T。
- CreateBiTree(&T,defintion)
- 初始条件:definition 给出二叉树 T 的定义。
- 操作结果:按照 definition 构造二叉树 T。
- ClearBiTree(&T)
- 初始条件:二叉树 T 存在。
- 操作结果:将二叉树 T 清空为空树。
- BiTreeEmpty(T)
- 初始条件:二叉树 T 存在。
- 操作结果:若 T 为空二叉树,则返回 true,反之返回 false
- BiTreeDepth(T)
- 初始条件:二叉树 T 存在。
- 操作结果:返回 T 的深度。
- Root(T)
- 初始条件:二叉树 T 存在。
- 操作结果:返回 T 的根。
- Value(T,e)
- 初始条件:二叉树 T 存在,e 是 T 中的某个结点。
- 操作结果:返回 e 的值。
- Assign(T,e,value)
- 初始条件:二叉树 T 存在,e 是 T 中的某个结点。
- 操作结果:将 value 的值赋给 e。
- Parent(T,cur_e)
- 初始条件:二叉树 T 存在,cur_e 是 T 中的某个结点。
- 操作结果:若 e 是 T 的非根结点,则返回它的双亲,反之返回空。
- Leftchild(T,e)
- 初始条件:二叉树 T 存在,cur_e 是 T 中的某个结点。
- 操作结果:则返回 e 的左孩子,反之返回空。
- Rightchild(T,e)
- 初始条件:二叉树 T 存在,e 是 T 中的某个结点。
- 操作结果:返回 e 的右孩子,反之返回空。
- LefttSibling(T,cur_e)
- 初始条件:二叉树 T 存在,e 是 T 中的某个结点。
- 操作结果:返回 e 的左兄弟,若 e 是 二叉树 T 的右孩子或无左兄弟,则返回空。
- RightSibling(T,cur_e)
- 初始条件:二叉树 T 存在,cur_e 是 T 中的某个结点。
- 操作结果:返回 e 的右兄弟,若 e 是 二叉树 T 的左孩子或无左兄弟,则返回空。
- InsertChild(&T,p,LR,c)
- 初始条件:二叉树 T 存在,p 指向 T 中某个结点,LR 为 0 或 1,非空二叉树 c 与 T不相交且右子树为空。
- 操作结果:根据 LR 为 0 或 1,插入 c 为 T 中 p 所指向结点的左或右子树。p 所指向结点的原有左或右子树则成为 c 的右子树
- DeleteChile(&T,p,LR)
- 初始条件:二叉树 T 存在,p 指向 T 中某个结点,LR 为 0 或 1。
- 操作结果:根据 LR 为 0 或 1,删除 T 中 p 所指向结点的左或右子树
- PreOrderTraverse(T)
- 初始条件:二叉树 T 存在。
- 操作结果:先序遍历 T,对每个结点访问一次。
- InOrderTraverse(T)
- 初始条件:二叉树 T 存在。
- 操作结果:中序遍历 T,对每个结点访问一次。
- PostOrderTraverse(T)
- 初始条件:二叉树 T 存在。
- 操作结果:后序遍历 T,对每个结点访问一次。
- LevelOrderTraverse(T)
- 初始条件:二叉树 T 存在。
- 操作结果:层次遍历 T,对每个结点访问一次。
5. 二叉树的性质和存储结构
性质1
- 在二叉树的第 i 层上至多有 2 i-1 个结点(i >= 1)。
性质2
- 深度为 k (这棵二叉树有k层)的二叉树至多 有 2k-1 个结点(k >= 1)。
- 证明:由性质 1 可知,深度为 k 的二叉树的最大结点数为:将每一层的结点全部加起来。
- 深度为 k 的二叉树至少有 k 个结点,每层至少得有 1 个结点。
性质3
-
对任何一个二叉树 T ,如果其终端结点数(叶子数)为 n0,度为2的结点树为 n2,则n0 = n2 + 1
- 如:下图有7 8 9 10 11 12这 6 个叶子结点,度为 2(有2个分支)的结点则有12345 这5个,所以叶子结点的数量 = 度为2的结点数+1,6=5+1
- 如:下图有7 8 9 10 11 12这 6 个叶子结点,度为 2(有2个分支)的结点则有12345 这5个,所以叶子结点的数量 = 度为2的结点数+1,6=5+1
-
证明
- 从下往上看:所有的结点都和它的双亲有一条边连着(除了根结点),设B为分支总数,所以分支总数 = 结点总数 - 根结点B = n - 1。
- 从上往下看:每个度为 2 的结点都会产生两条边,度为 1 的结点则产生 1 条边,剩余的叶子结点因为没有分支,所以不产生边,所以总边数为:度为2的结点数 x 2 + 度为 1 的结点 x 1,B = n₂ x 2 + n₁ x 1。
- 同一棵二叉树的边的个数应该是相等的,所以,不管从上往下看还是从下往上看的结果应该相等。
6.满二叉树和完全二叉树
6.1满二叉树
- 一颗深度为 k 且含有 2k-1个结点的二叉树称为满二叉树,顾名思义,一个二叉树中的所有结点都存在。
- 如:这棵满二叉树就有 24-1 = 15个结点
- 每一层上的结点树都是最大结点数(即每层都满)。
- 叶子结点全部在最底层。
- 满二叉树在同样深度的二叉树中结点个数最多
- 满二叉树在同样深度的二叉树中叶子结点个数最多
6.2完全二叉树
- 满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。
- 深度为 K 的具有 n 个结点的二叉树,当且仅当其每一个结点都与深度为 k 的满二叉树中编号为 1~n 的结点一一对应时,称之为完全二叉树。
- 如:下图的完全二叉树的全部编号都能在满二叉树当中找到。编号 123456 的位置,都能在满二叉树的对应位置上找到。
非完全二叉树
- 非完全二叉树就不是所有的编号都能在满二叉树对应的位置上找到了。
- 如下图:非完全二叉树编号为 6 的结点就不能在满二叉树中编号为 6 的位置上找到了。
- 如下图:非完全二叉树编号为 6 的结点就不能在满二叉树中编号为 6 的位置上找到了。
更好的完全二叉树判别方法
- 在满二叉树中,从最后一个结点开始,连续去掉任意个结点,即是一颗完全二叉树。
- 注:一定是连续的去掉!!!
完全二叉树的特点
- 叶子只可能分布在层次最大的两层上(叶子只会在最后1层或倒数第二层)。
- 对任一结点,如果其右子树的最大层次为 i ,则其左子树的最大层次必为 i 或 i + 1.(相差不超过1)
6.3 完全二叉树的性质
性质4
性质5
如果对一棵有 n 个结点的完全二叉树(深度为𠃊log2n𠃎 + 1)的结点按层序编号(从第一层到𠃊log₂n𠃎 + 1层,每层从左到右),则队任一结点 i(1 <= i <= n),有:
- 如果 i = 1,则结点 i 是二叉树的根,无双亲;如果 i > 1,则其双亲是结点𠃊 i / 2 𠃎。
- 如果 2i > n,则结点 i 为叶子结点,无左孩子了否则,其左孩子是结点 2i。
- 如果 2i + 1 > n,则结点 i 无右孩子;否则其右孩子是结点 2i + 1。
7. 二叉树的存储结构
- 类似线性表,二叉树的顺序存储结构也可采用顺序存储和链式存储两种方式。
7.1二叉树的顺序存储
- 按照满二叉树的结点层次编号,依次存放二叉树中的数据元素。
- 每个结点的编号就作为数组的下标,根据结点编号存放在数组中。
- 如:根节点的编号就是 0,然后按照自上而下,自左而右的顺序依次排号。
7.1.1 二叉树的顺序存储特点
缺点
- 顺序存储共有一个缺点就是存储元素的数组大小是固定的
- 当加入树的元素个数变化很大时,这时还用定长的数组存储就不够灵活了
- 最坏的情况就如下图所示,这样一颗右单支树的空间利用率就相当低,为了这样一颗树需要7个元素的数组。
- 右单支数的成熟越高,越浪费空间
特点
- 结点空间关系蕴含在其存储位置中
- 浪费空间,适合于存储满二叉树和完全二叉树
7.2 二叉树的链式存储结构
- 由二叉树的定义可知,二叉树的结点有一个数据元素和分别指向其左、右子树的两个分支构成。
- 则表示二叉树的链表中的结点至少包括三个域:数据域和左、右指针域
- 左、右指针分别指向左、右孩子,如果要经常操作结点的左右孩子,就使用这种存储结构。
typedef struct BiNode {
int data;
struct BiNode *child_life, *child_right; //左右孩子指针
}BiNode, *BiTree;
7.2.1 二叉链表
- 二叉链表有两个指针域,一个指向左孩子,另一个指向右孩子。
- 用一个头指针指向整棵二叉树的根结点 A,
- 没有孩子的那一边的指针域置为 NULL。
- 如:A 结点没有右孩子,所以将A结点的右指针域置为NULL,其他结点同理。
二叉链表的规律
- 在有 n 个结点的二叉链表中,有 n + 1 个空指针域。
- 分析:必定有 2n 个链域。除根节点之外,每个结点有且仅有一个双亲,所以只会有 n - 1个结点的链域用来存放指针,指向非空子女结点。