【基础前置】树
T r e e Tree Tree
一棵树,一棵树
彼此孤离地兀立着
风与空气
告诉着他们的距离
但是在泥土的覆盖下
他们的根伸长着
在看不见的深处
他们把根须纠缠在一起
\quad \quad \quad \quad \quad \quad \quad \quad \quad \quad ——《树》艾青
树是什么?
树是 n n n ( n ⩾ 0 ) (n \geqslant0) (n⩾0) 个 有限结点 组成的一个具有 层次关系 的 集合。
有一些重要的概念(若无需要可以跳过)
概念
-
根节点
-
没有直接前驱,但有零个或多个直接后继的结点。
结点的度
-
一个结点的子树个数称为此结点的度。
- 如上图,结点1的度为3。 叶子结点
-
度为0的结点,即无后继的结点,也称为终端结点。
分支结点
-
度不为0的结点,也称为非终端结点。
孩子结点
-
一个结点的直接后继称为该结点的孩子结点。
图中圈出的结点为结点1的孩子结点。
双亲结点
-
一个结点的直接前驱称为该结点的双亲结点。
结点1为结点2、3、4的双亲结点。
兄弟结点
-
同一双亲结点的孩子结点之间互称兄弟结点。
祖先结点
-
一个结点的祖先结点是指从根结点到该结点的路径上的所有结点。
结点1、2、5是结点9的祖先。
子孙结点
-
一个结点的直接后继和间接后继称为该结点的子孙结点。
结点5、6、9、10是结点2的子孙节点。
树的度
-
树中所有结点的度的最大值。
如上面那棵树的度就为3。
结点的层次
- 从根结点开始定义,根结点的层次为1,根的直接后继的层次为2,依此类推。 树的高度(深度)
- 树中所有结点的层次的最大值。 森林
- 若干互不相交的树的集合。 二叉树
-
每个结点的度都不大于2且每个结点的孩子结点次序不能任意颠倒的树。
满二叉树
-
每层结点都具有最大结点数的二叉树。
完全二叉树
-
一棵深度为
k
k
k 的有
n
n
n 个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为
i
(
1
⩽
i
⩽
n
)
i(1\leqslant i\leqslant n)
i(1⩽i⩽n)的结点与满二叉树中编号为
i
i
i 的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
一些性质
-
性质1.1
-
描述:
在二叉树的第
i
i
i 层上至多有
2
i
−
1
2^{i-1}
2i−1个结点 。
证明: -
当
i
=
1
i=1
i=1 时,整个二叉树只有一根结点,此时
2
i
−
1
=
2
0
=
1
2i-1=2^0=1
2i−1=20=1,结论成立。
归纳假设:假设 i = k i=k i=k 时结论成立,即第 k k k 层上结点总数最多为 2 k − 1 2k-1 2k−1 个。
现证明当 i = k + 1 i=k+1 i=k+1 时, 结论成立:
因为二叉树中每个结点的度最大为 2 2 2 ,则第 k + 1 k+1 k+1 层的结点总数最多为第 k k k 层上结点最大数的 2 2 2 倍,即 2 × 2 k − 1 = 2 ( k + 1 ) − 1 2×2^{k-1}=2^{(k+1)-1} 2×2k−1=2(k+1)−1 ,故结论成立。
性质1.2
-
描述:
深度为
k
k
k 的二叉树至多有
2
k
−
1
2^k-1
2k−1 个结点
(
k
⩾
1
)
(k \geqslant 1)
(k⩾1) 。
证明:
设该二叉树第 i i i 层上有 n i n_i ni 个结点。 - ∵ n i ⩽ 2 i − 1 \because n_i \leqslant 2^{i-1} ∵ni⩽2i−1(已证)
- ∴ ∑ i = 1 k n i ⩽ ∑ i = 1 k 2 i − 1 \therefore \sum_{i=1}^kn_i\leqslant\sum_{i=1}^k2^{i-1} ∴∑i=1kni⩽∑i=1k2i−1
- ∴ ∑ i = 1 k n i ⩽ 2 k − 1 \therefore \sum_{i=1}^kn_i\leqslant2^k-1 ∴∑i=1kni⩽2k−1 性质1.3
-
描述:
对任意一棵二叉树
T
T
T,若终端结点数为
n
0
n_0
n0,而其度数为
2
2
2 的结点数为
n
2
n_2
n2,则
n
0
=
n
2
+
1
n_0=n_2+1
n0=n2+1 。
证明: -
设
T
T
T 中结点的度为
1
1
1 的结点数为
n
1
n_1
n1,总结点数为
n
n
n,则有
n 0 + n 1 + n 2 = n n_0+n_1+n_2=n n0+n1+n2=n
设 T T T 中共有 B B B 条边,则 B = n − 1 B = n-1 B=n−1 。
由定义可知终端节点无出边,度数为 1 1 1 的结点有 1 1 1 条出边,度数为 2 2 2 的结点有 2 2 2 条出边,则
B = n 1 + 2 n 2 B = n_1+2n_2 B=n1+2n2
联立得
n 1 + 2 n 2 = n − 1 n_1+2n_2=n-1 n1+2n2=n−1
将 n = n 0 + n 1 + n 2 n = n_0+n_1+n_2 n=n0+n1+n2 代入得
n 1 + 2 n 2 = n 0 + n 1 + n 2 − 1 n_1+2n_2=n_0+n_1+n_2-1 n1+2n2=n0+n1+n2−1
化简整理得 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1 。
性质1.4
-
描述:
具有
n
n
n 个结点的完全二叉树的深度为
⌊
log
2
n
⌋
+
1
\lfloor\log2^n\rfloor+1
⌊log2n⌋+1。
证明: -
设
n
n
n 个结点的完全二叉树的深度为
k
k
k,根据性质1.2可知,
k
−
1
k-1
k−1层满二叉树的结点总数为
n 1 = 2 k − 1 − 1 n_1=2^{k-1}-1 n1=2k−1−1
k k k 层满二叉树的结点总数为
n 2 = 2 k − 1 n_2=2^k-1 n2=2k−1
显然有 n 1 < n ⩽ n 2 n1<n\leqslant n2 n1<n⩽n2,进一步可以推出 n 1 + 1 ⩽ n < n 2 + 1 n1+1\leqslant n<n2+1 n1+1⩽n<n2+1 。
将 n 1 = 2 k − 1 − 1 n_1=2^{k-1}-1 n1=2k−1−1 和 n 2 = 2 k − 1 n2=2^k-1 n2=2k−1代入上式,可得 2 k − 1 ⩽ n < 2 k 2^{k-1}\leqslant n<2^k 2k−1⩽n<2k,即 k − 1 ⩽ log 2 n < k k-1\leqslant \log2n<k k−1⩽log2n<k 。
又因为 k k k 为整数,所以 k − 1 = ⌊ log 2 n ⌋ k-1=\lfloor\log2n\rfloor k−1=⌊log2n⌋, k = ⌊ log 2 n ⌋ + 1 k=\lfloor\log2n\rfloor+1 k=⌊log2n⌋+1, 故结论成立。
之后的树主要指的是二叉树。
树怎么用?
存储
二叉树的存储结构有两种: 顺序存储结构 和 链式存储结构 。
-
顺序存储结构
-
类似bfs保存的路径。
用顺序存储结构存下该二叉树:
现在似乎没问题。
但如果是一般的二叉树:
结果是:
其中 ^ 是空。
所以,当二叉树为非完全二叉树时,顺序存储结构会将空的子树进行存储,浪费大量空间。
链式存储结构
-
在每一个结点中存储该结点的数据信息,左子树(左孩子结点)信息,右子树(右孩子结点)信息。需要时还可以存储其他信息。
可以用链表存储,分为数据域,左子树域,右子树域。
但由于链表中的指针难以理解,因此可以用数组模拟链表。
由于有时树为非二叉树,孩子结点可以用数组储存。
遍历
-
常规的有前序遍历,中序遍历,后序遍历。
前序遍历也叫先根遍历,同理,中序遍历也叫中根遍历,后序遍历也叫后根遍历。
先序遍历一般指先遍历树的根,再遍历左子树、右子树。
路径:ABDGHCEIJF
中序遍历一般指先遍历左子树,再返回来遍历根,接着遍历右子树。
路径:GDHBAIECFJ
后序遍历一般指先遍历左子树,再遍历右子树,最后遍历根。
路径:GHDBIEJFCA
一些模板
非二叉树
树的存储
struct node {
int data;
int child[10];
int father; // 父结点信息
} tree[1000005];
找根
int root; //根结点下标
for(int i = 1; i <= n; i ++) {
if(!tree[i].father) { //没爸的就是根
root = i;
break;
}
}
二叉树
存储
struct node {
int data;
int left_child;
int right_child;
} tree[1000005];
找根
int root;
for(int i = 1; i <= n; i ++) {
if(!tree[i].father) {
root = i;
break;
}
}
前序遍历
void pre_order(int x) {
if(!tree[x].data) { //虚点的概念
return;
}
printf("%d ", tree[x].data); //根
pre_order(tree[x].left_child); //左子树
pre_order(tree[x].right_child); //右子树
}
中序遍历
void in_order(int x) {
if(!tree[x].data) {
return;
}
in_order(tree[x].left_child);
printf("%d ", tree[x].data);
in_order(tree[x].right_child);
}
后序遍历
void post_order(int x) {
if(!tree[x].data) {
return;
}
post_order(tree[x].left_child);
post_order(tree[x].right_child);
printf("%d ", tree[x].data);
}
求二叉树深度
int Max = -1; //Max为深度
void dfs(int root, int deep) {
if(!tree[root].child[0] && !tree[root].child[1]) { //为叶子结点
Max = max(Max, deep);
}
if(a[root].child[0]) { //左子树不为空
dfs(a[root].child[0], deep + 1);
}
if(a[root].child[1]) { //右子树不为空
dfs(a[root].child[1], deep + 1);
}
}
树有哪些?
平衡二叉树、排序二叉树、哈夫曼树、前缀字典树、后缀字典树、基数字典树、线段树、B树、红黑树、B+树、平衡二叉查找树、二叉堆、区间红黑树、B*树、2-3树、2-3-4树、四叉树、八叉树、K-D树、R树、R+树、R*树、伸展树、AAA树、李超线段树、主席树、划分树、替罪羊树、笛卡尔树、左偏红黑树、WBLT、珂朵莉树、LCT、ETT、析合树、PQ 树、手指树。
好大一棵二叉树
黄昏,暮霭漫漫
谁在寂寞地遍历树上每一个结点?
从无边的代码中抬起头
夜色,淹没了每一字节。