树
- 树的最基本概念(略);
- 树型结构是一类非常重要的非线性数据结构,因为不是线性的,所以保存时没有数组那么容易和方便。
- 每颗树都有一个根结点(因此结点数量 n ≥ 1 n \ge 1 n≥1)。没有父结点的树结点称为根结点;
- 每个节点都可以有0个或多个子结点,子结点数量为0的结点称为叶节点。
相关概念
- 结点的度:一个结点含有的子结点的个数称为该结点的度;
- 树的度:一棵树中,最大的结点的度称为树的度;
- 叶结点或终端结点:度为0的结点称为叶结点;
- 结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推;
- 树的高度或深度:定义一棵树的根结点层次为1,其他结点的层次是其父结点层次加1。一棵树中所有结点的层次的最大值称为这棵树的深度。
- 森林:由棵互不相交的树的集合称为森林。
树的存储结构:
- 树的保存主要以链表的结构保存,下面是3种常用的保存方式:
双亲表示法
- 定义一个结构体或类,用来保存结点数据以及它的父节点地址。将所有的结点保存成结构体数组,这样方便遍历访问。
- 也即每个节点设置一个变量保存父节点的地址,根节点的变量值设置为空。
class node {
int parentIndex; // 父结点的数组索引值
Data data; // 结点数据对象
}
孩子表示法:
- 每个结点都保存它所有孩子节点的地址,因为不同的结点其孩子节点的数量可能会不一致,所以这里一般采用链表的形式保存孩子节点地址。
- 也即将双亲表示法中父节点地址替换成链表首地址。
]
)
孩子兄弟表示法
- 该方法又称二叉树表示法,或称二叉链表表示法。
- 链表中设置两个固定的指针域,第一个指针域指向子一个孩子节点,第二个指针域指向下一个兄弟节点。这样每个结点的指针域都是固定的,方便分配内存空间。
二叉树
- 二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。
- 二叉树特点是每个结点最多只能有两棵子树,且有左右之分。
- 二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树。
- 在研究二叉树的一般性质是,我们先来研究一下几种特殊的二叉树。
满二叉树
- 如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
- 除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。
满二叉树的性质
- 从上到下,一个满二叉树每层的节点数分别为:1,2,4,8,16…;容易发现他们都是2的倍数。可以表示为: 2 0 , 2 1 , 2 2 , 2 3 , 2 4 2^0,2^1,2^2,2^3,2^4 20,21,22,23,24…;
- 也即满二叉树第
i
层上的结点数为: 2 i − 1 2^{i-1} 2i−1。因此深度为k
的一个满二叉树,它的叶子节点数量为:
2 k − 1 2^{k-1} 2k−1
- 非叶子节点的数量(前 k − 1 k-1 k−1层)为:
2 0 + 2 1 + . . . + 2 k − 2 = 2 k − 1 − 1 2^{0} + 2^{1} + ... + 2^{k-2} = 2^{k-1}-1 20+21+...+2k−2=2k−1−1
- 满二叉树的叶子结点数比非叶子结点数量多一个;
- 第k层最左边第一个叶子结点的编号为:
( 2 k − 1 − 1 ) + 1 = 2 k − 1 (2^{k-1}-1)+1=2^{k-1} (2k−1−1)+1=2k−1
-
也即如果一个节点的 i ≥ 2 k − 1 i\ge 2^{k-1} i≥2k−1,那么这个节点一定是叶子节点,反之则是非叶子节点。
-
一个层数为 k k k的满二叉树总结点数为:
2 k − 1 + 2 k − 1 − 1 = 2 k − 1 2^{k-1} + 2^{k-1} - 1 = 2^k - 1 2k−1+2k−1−1=2k−1
- 2 k − 1 2^{k}-1 2k−1一定是一个奇数,因此满二叉树的节点数量一定是奇数个。
- 设层数为k的满二叉树的总结点数为n,则他们满足:
2 k − 1 = n k = l o g 2 ( n + 1 ) 2^k - 1 = n\\ k = log_2(n+1) 2k−1=nk=log2(n+1)
- 设某一个结点的编号为 i i i,则它的父节点的编号为: ⌊ i / 2 ⌋ \lfloor i/2 \rfloor ⌊i/2⌋,如果该节点有子孙节点( i < 2 k − 1 i < 2^{k-1} i<2k−1),则其左孩子节点的编号为: 2 i 2i 2i,右孩子节点的编号为: 2 i + 1 2i+1 2i+1。
完全二叉树
- 一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为 i ( 1 ≤ i ≤ n ) i(1\le i \le n) i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
- 满二叉树算是一种特殊的完全二叉树。
完全二叉树的性质:
- 具有n个节点的完全二叉树的深度为 ⌊ l o g 2 x ⌋ + 1 \lfloor log_2x \rfloor + 1 ⌊log2x⌋+1。推到过程如下:
2 k − 1 ≤ n < 2 k k − 1 ≤ l o g 2 n < k k = ⌊ l o g 2 x ⌋ + 1 2^{k-1} \le n < 2^k \\ k-1 \le log_2n < k \\ k = \lfloor log_2x \rfloor + 1 2k−1≤n<2kk−1≤log2n<kk=⌊log2x⌋+1
- 我们知道,在满二叉树中,结点
i
i
i的左孩子节点为
2
i
2i
2i,有孩子结点为
2
i
+
1
2i+1
2i+1;所以在完全二叉树中,有:
- 如果结点的编号满足 2 i ≤ n 2i \le n 2i≤n,,说明其左孩子节点存在,且编号为 2 i 2i 2i;
- 如果结点的编号满足 2 i + 1 ≤ n 2i+1 \le n 2i+1≤n,,说明其左右孩子节点都存在,且编号分别为 2 i 2i 2i和 2 i + 1 2i+1 2i+1;
- 满二叉树的绝大部分性质都适用于完全二叉树,具体的就不细说了。
二叉树的性质
- 从满二叉树和完全二叉树的性质中,我们可以提炼出一些通用的二叉树性质:
-
二叉树的第 i i i层上最多(满二叉树状态下)有 2 i − 1 2^{i-1} 2i−1个节点( i ≥ 1 i\ge1 i≥1)。
-
深度为k的二叉树最多(满二叉树状态下)有 2 k − 1 2^k-1 2k−1个节点。
-
对于任何一颗二叉树T,如果其叶子结点数为 n 0 n_0 n0,度为2的非叶子节点数为 n 2 n_2 n2,,则一定有:
n 0 = n 2 + 1 n_0 = n_2 + 1 n0=n2+1
(证明过程)设B为分支(边)总数,节点总数为n,度为1的非叶子节点数为 n 1 n_1 n1,则:
- n = n 0 + n 1 + n 2 n = n_0 + n_1 + n_2 n=n0+n1+n2
- 从连接向父结点的角度考虑B:由于除根结点外,每个结点有一条边连向父节点,所以有: B = n − 1 B=n-1 B=n−1;
- 从连接到孩子节点的角度考虑B:度为2的节点必然由两条边连向左右孩子节点,而度为1的节点只有一条,度为0的节点没有,所以有: B = n 1 + 2 n 2 = n − 1 B = n_1 + 2n_2 = n - 1 B=n1+2n2=n−1
- 结合上面的表达式可以得出: n 0 = n − n 1 − n 2 = n 1 + 2 n 2 + 1 − n 1 − n 2 = n 2 + 1 n_0 = n - n_1 - n_2 = n_1 + 2n_2 + 1 - n_1 - n_2 = n_2 + 1 n0=n−n1−n2=n1+2n2+1−n1−n2=n2+1。
二叉树的深度遍历
- 按深度优先搜索对二叉树进行遍历,根据访问当前节点和左右子树的顺序不同,主要分为三种模式:
先序遍历:
- 访问根节点(当前节点);
- 先序遍历左子树(递归操作);
- 先序遍历右子树(递归操作);
中序遍历:
- 中序遍历左子树(递归操作);
- 访问根节点(当前节点);
- 中序遍历右子树(递归操作);
后续遍历:
- 后序遍历左子树(递归操作);
- 后序遍历右子树(递归操作);
- 访问根节点(当前节点);