文章目录
前言
本篇章主要介绍树的基本知识,包括树的定义、基本术语、性质、表示方式、存储方式及其三种遍历。
1. 树的定义
树
(
T
r
e
e
)
(Tree)
(Tree)是
n
(
n
≥
0
)
n(n \geq 0)
n(n≥0)个结点的有限集合。当
n
=
0
n=0
n=0时,称为空树。在任意一棵非空树中应满足:
(1) 有且仅有一个树根结点,即树的根结点;
(2) 当
n
>
1
n>1
n>1时,其余结点可分为
m
(
m
>
0
)
m(m>0)
m(m>0)个互不相交的有限集
T
1
,
T
2
,
.
.
.
,
T
m
T_1,T_2,...,T_m
T1,T2,...,Tm,其中每个集合本身又是一棵树,称为根的子树。
综上可知,树是一种递归的数据结构,同时也是一种分层结构,具有以下两个特点:
(1) 树的根结点没有前驱,除根结点外的所有结点有且仅有一个前驱(即其父结点);
(2) 树中所有结点可以有零个或多个后继(即其子女结点)。
2. 基本术语
结点的度:每个结点拥有子树的数目。结点A的度为5,B的度为2,C的度为0。
树的度:树内所有结点的度的最大值。该树的度为5.
叶子结点:度为0的结点,又称为终端结点。结点C、G、H均为叶子结点。
分支结点:度不为0的结点,又称为非终端结点。结点B、D均为分支结点。
孩子结点:树中任何一个结点的子树的根结点,又称为后继结点。对于根结点A,结点B、C、D均为其孩子结点。
双亲结点:对于树中任何一个结点而言,若其具有孩子结点,则这个结点就称为其孩子结点的双亲结点。根结点A是结点B、C、D的双亲结点。
兄弟结点:同一双亲结点的孩子结点互相成为兄弟结点。结点B、C、D、E、F互称兄弟结点。
祖先结点:从根结点到树中任一结点所经过的所有结点都称为该结点的祖先结点。从结点A到结点L,中间还经过结点F和结点K,所以结点A、F、K都称为结点L的祖先结点。
子孙结点:树中以某一结点为根的子树中任一结点均被称为该根结点的子孙结点。以结点F为根的子树,其结点K、L、M都称为结点F的子孙结点。
堂兄弟结点:双亲结点在同一层次的结点称为堂兄弟结点。结点G、I、J、K的双亲结点都在第二层,它们互称为堂兄弟结点。
结点的层次:从根结点开始定义,根结点为第一层,其孩子结点为第二层,以此类推。
结点的深度:从根结点开始自顶向下逐层累加。
结点的高度:从叶结点开始自底向上逐层累加。
树的深度:树中所有结点的层次的最大值,也称为树的高度。上述的树的深度为4。
有序树:树中节点的各子树从左到右是有次序的,位置不能互换,则称该树为有序树。否则,为无序树。图中所示的树就是一个有序树,若将子结点互换后就变成了另一棵树。
路径:树中两结点之间的边,由两结点之间所经过的结点序列构成。
路径长度:路径上所经过的边的个数。
分支:树中某一结点与其孩子结点间的连线,比如分支AB。
权值:树中的结点会被赋予一个有某种意义的数值,比如该结点被访问的概率
3. 基本性质
树具有以下最基本的性质(具体推导过程请参阅二叉树篇):
(1) 树中的结点
=
=
= 所有结点的度
+
+
+ 1;
(2) 度为
m
m
m的树中第
i
i
i层至多有
m
i
−
1
m^{i-1}
mi−1个结点;
(3) 高度为
h
h
h的
m
m
m叉树至多有
m
h
−
1
m
−
1
\frac {m^h - 1} {m - 1}
m−1mh−1个结点;
(4) 具有
n
n
n个结点的
m
m
m叉树的最小高度为
⌈
log
m
(
n
(
m
−
1
)
+
1
)
⌉
\lceil \log_m (n(m-1)+1)\rceil
⌈logm(n(m−1)+1)⌉。
⌈ x ⌉ \lceil x \rceil ⌈x⌉:向上取整,即不小于 x x x的最小整数; ⌊ x ⌋ \lfloor x \rfloor ⌊x⌋:向下取整,即不大于 x x x的最大整数。如 ⌈ 2.5 ⌉ = 3 , ⌊ 2.5 ⌋ = 2 \lceil 2.5 \rceil=3,\lfloor 2.5 \rfloor=2 ⌈2.5⌉=3,⌊2.5⌋=2.
4. 树的表示
树的表示有很多种方法,常见的表示方法有树形结构表示法、凹入表示法(凹入法)、嵌套集合表示法(文氏图表示法)、广义表表示法(括号表示法)。
(1) 树形结构表示法:用圆圈代表结点,圆圈的内容代表结点的数据,圆圈之间的连线代表结点间的关系。
(2) 凹入表示法:使用条形来代表结点,孩子结点比双亲结点条形长度更短,同一层的结点的条形长度相同。
(3) 嵌套集合表示法:用圆圈代表结点,圆圈间的隶属关系代表树中结点之间的关系,它们可以是包含关系,也可以是不包含关系。
(4) 广义表表示法:用括号中的元素来代表结点,括号间的包含关系代表树中结点间的关系。
(
A
(
B
(
G
,
H
)
,
C
,
D
(
I
)
,
E
(
J
)
,
F
(
K
(
L
,
M
)
)
)
)
\Bigg(A\bigg(B\Big(G,H\Big),C,D\Big(I\Big),E\Big(J\Big),F\Big(K(L,M)\Big)\bigg)\Bigg)
(A(B(G,H),C,D(I),E(J),F(K(L,M))))
5. 存储方式
5.1 双亲表示法
这种存储方式采用一组连续空间来存储每个结点,同时在每个结点中增设一个伪指针parent,用来指示其双亲结点在数组中的位置(即数组下标)。根结点没有双亲结点,可将其parent设置为-1。
index | data | parent |
---|---|---|
0 | A | -1 |
1 | B | 0 |
2 | C | 0 |
3 | D | 0 |
4 | E | 1 |
5 | F | 1 |
6 | G | 3 |
7 | H | 6 |
8 | I | 6 |
9 | J | 6 |
这种方法在寻找某个结点的双亲结点是很容易实现,但是在寻找某个结点的孩子结点时,最坏的情况要访问整个数组。
class TreeNode(object):
def __init__(self):
self.data = '#'
self.parent = -1
5.2 孩子表示法
孩子表示法是将每个结点的孩子结点都用单链表连接起来,形成一个线性结构,此时 n n n个结点就有 n n n个孩子链表(叶子结点的孩子链表为空)。
这种存储方式寻找在寻找结点的子女结点是很容易,但是在寻找结点的双亲结点时需要遍历
n
n
n个结点中孩子链表指针域所指向的
n
n
n个孩子链表。
class TreeNode(object):
def __init__(self):
self.data = '#'
self.first_child = None
class ChildNode(object):
def __init__(self):
self.index = -1
self.next_sibling = None
5.3 孩子兄弟表示法
(左)孩子(右)兄弟表示法又称为二叉树表示法,即以二叉链表作为树的存储结构。包括三部分:结点值、指向结点第一个孩子结点的指针、指向结点下一个兄弟结点的指针(沿此域可以找到结点的所有兄弟结点)。
这种存储方式比较灵活,可以很方便地将树转换为二叉树,易于查找结点的孩子结点,但从当前结点查找其双亲结点比较麻烦。若为每个结点增设一个parent域指向其父结点,则查找结点的父结点也很方便。
class TreeNode(object):
def __init__(self):
self.data = '#'
self.first_child = None
self.next_sibling = None
6. 树的遍历
树的遍历是指通过某种方式访问树中的每个结点,且每个结点仅访问一次。
6.1 先根遍历
先根遍历过程如下:
(1) 先访问根结点;
(2) 再依次遍历根结点的每棵子树,遍历子树时仍遵循先根后子树的规则。
上述的树采用先根遍历得到的结点序列为:
A
B
E
K
L
F
C
G
D
H
M
I
J
ABEKLFCGDHMIJ
ABEKLFCGDHMIJ。
6.2 后根遍历
后根遍历过程如下:
(1) 先依次遍历根结点的每棵子树;
(2) 再访问根结点,遍历子树时仍遵循先子树后根的规则。
上述的树采用后根遍历得到的结点序列为:
K
L
E
F
B
G
C
M
H
I
J
D
A
KLEFBGCMHIJDA
KLEFBGCMHIJDA。
6.3 层次遍历
层次遍历的过程是从根结点开始,按结点所在的层次从上到下、从左到右的次序依次访问树中的每一个结点。
上述的树采用层次遍历得到的结点序列为:
A
B
C
D
E
F
G
H
I
G
K
L
M
ABCDEFGHIGKLM
ABCDEFGHIGKLM。