树的相关定义
定义:树是n(n>=0)个结点的有限集。n=0称为空树。在任意一颗非空树中:
1)有且仅有一个特定的称为根的结点
2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集
T
1
,
T
2
,
.
.
.
,
T
m
T_1, T_2, ..., T_m
T1,T2,...,Tm, 其中每一个集合本身又是一颗树,并称为根的子树。
度
结点拥有的子树数称为结点的度, 度为0的结点称为叶结点或终端结点;度不为0的结点称为非终端结点或分支结点,树的度是树内结点的度的最大值。
深度(高度)
树中结点的最大层次称为树的深度或高度
注意两者度和深度概念的区别,在上图中,度为3,而深度为4
树的存储结构
充分利用顺序存储和链式存储结构的特点,实现对树的存储结构的表示,以下介绍三种表示方法
双亲表示法
假设以一组连续空间存储树的结点,同时在每个结点中,附设一个指示器指示其双亲结点在数组中的位置,也就是,每个结点除了知道自己是谁之外,还知道它的双亲在哪里。
data | parent |
---|
其中data是数据域,存储结点的数据信息,parent是指针域,存储该结点的双亲在数组的下标
根据上图,可以得到如下的存储结构,其中约定根结点的位置域设置为-1
下标 | data | parent |
---|---|---|
0 | A | -1 |
1 | B | 0 |
2 | C | 0 |
3 | D | 0 |
4 | E | 1 |
5 | F | 1 |
6 | G | 2 |
7 | H | 3 |
8 | I | 3 |
9 | J | 3 |
10 | K | 4 |
11 | L | 4 |
12 | M | 7 |
以上的存储结构,我们可以很快地根据结点找出对应的双亲的结点,时间复杂度为O(1),但是如果想知道结点的孩子呢?我们需要遍历整个结构。怎么改进呢?通过增加一个结点最左边孩子的域,先称为长子域,约定没有孩子的结点,这个长子域设为-1,那么有
下标 | data | parent | firstchild |
---|---|---|---|
0 | A | -1 | 1 |
1 | B | 0 | 4 |
2 | C | 0 | 6 |
3 | D | 0 | 7 |
4 | E | 1 | 10 |
5 | F | 1 | -1 |
6 | G | 2 | -1 |
7 | H | 3 | 12 |
8 | I | 3 | -1 |
9 | J | 3 | -1 |
10 | K | 4 | -1 |
11 | L | 4 | -1 |
12 | M | 7 | -1 |
以上的结构解决了含有0或1个孩子的结点找孩子的问题。如果更关注兄弟间的关系,双亲表示法可以增加一个右兄弟关系,即每一个结点如果存在右兄弟,则记录下右兄弟的下标。
孩子表示法
方法1
多重链表表示法:每个结点有多个指针域,其中每个指针指向一颗子树的根结点
data | child1 | child2 | … | childd |
---|
其中data是数据域。child1到childd是指针域,用来指向该结点的孩子结点。
对于上图来说,树的度是3,所以指针域的个数是3,实现如下图:
缺点:对于树中各结点的度相差很大时,造成空间浪费
方法2
每个结点指针域的个数等于该结点的度,专门取一个位置来存储结点指针域的个数
其中data为数据域,degree为度域,也就是存储结点的孩子结点的个数, child1到childd为指针域,指向该结点的各个孩子的结点
优点:克服了浪费空间的缺点,对空间利用率较高
缺点:各个结点的链表是不相同的结构,加上要维护结点的度的数值,时间复杂度较高
孩子表示法:把每个结点的孩子结点排列起来,以单链表作为存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针作为一个线性表,采用顺序存储结构,存放进一个一维数组,实现如下图:
孩子兄弟表示法
任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。,因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。