0. 绪论
树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合.把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。这一小节我们主要介绍一些关于树的一些知识,还有二叉树的一些重要的概念和知识.
1. 树的基本概念
1.1 树的定义
基本定义(递归定义): 树是由 N ( N ≥ 0 ) N(N\geq{0}) N(N≥0)个结点的有限集合, N = 0 N=0 N=0的时候称为空树,这是一种特殊的情况.在任意一棵非空树中应当满足:
- 有且仅有一个特定的称为根的结点;
- 当 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},\dots,T_{m} T1,T2,…,Tm,其中每一个集合体本身又是一棵树,并且称为根节点的子树.
树的特点:树有以下的两个具体的特点:
- 树的根节点没有前驱结点,除了根结点之外的所有结点有且只有一个前驱结点.
- 树中所有结点可以有零个或者多个后继结点.
1.2 树的基本术语
如上图所示
- 考虑到结点 K K K.根 A A A到结点 K K K的唯一路径上的任意结点称为 K K K的祖先结点,而结点 K K K是结点 B B B的子孙节点.路径上最接近结点 K K K的结点 E E E称为 K K K的双亲结点,而 K K K为结点 E E E的孩子结点.根 A A A是树上唯一没有双亲的结点.有相同双亲的结点称为兄弟结点(例如结点 K , L K,L K,L).
- 树中一个结点的子结点个数称为该结点的度,树中结点的最大度数称为树的度.
- 度大于0的结点称为分支结点(分终端结点);度为0(没有子女结点)的结点称为叶子结点(终端结点).
- 结点的深度、高度和层次
结点的层次是从树根开始定义,根的结点为第1层(有些教材中将根结点定义为0层),它的子结点为第2层,以此类推.
结点的深度是从根结点开始自顶向下逐层累加的.
结点的高度是从叶结点开始自底向上逐层累加的.
树的高度(又称为深度)是树中结点最大的层数. - 有序树和无序树:树中结点的子树从左向右是有次序的,不能交换,这样的树叫做有序树,有序树中其子结点按照从左向右是顺序出现是有关联的.反之则称为无序树.
- 路径和路径长度:树中两个结点之间的路径是由这两个结点之间所经过的结点序列构成的,而路径长度是路径上所经过的边的个数.
- 森林:森林是 m ( m ≥ 0 ) m(m\geq{0}) m(m≥0)棵互不相交的树的集合.
1.3 树的性质
树具有如下最基本的性质:
- 树中的结点数等于所有结点的度数加1.
- 度为 m m m的树中第 i i i层上至多有 m i − 1 m^{i-1} mi−1个结点( i ≥ 0 i\geq{0} i≥0).
- 高度为 h h h的 m m m叉树至多有 ( m h − 1 ) / ( m − 1 ) (m^{h}-1)/(m-1) (mh−1)/(m−1)个结点.
- 具有 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)⌉.
树的一些重要的计算公式:
① 总结点数量
=
N
0
+
N
1
+
N
2
+
⋯
+
N
m
=N_{0}+N_{1}+N_{2}+\dots+N_{m}
=N0+N1+N2+⋯+Nm;
② 总分支数量
=
1
×
N
1
+
2
×
N
2
+
⋯
+
m
×
N
m
=1\times{N_{1}}+2\times{N_{2}}+\dots+m\times{N_{m}}
=1×N1+2×N2+⋯+m×Nm;
③ 总结点数量
=
=
=总分支数
+
1
+1
+1;
这样通过三个等式可以得到
N
0
=
1
+
N
2
+
2
N
3
+
⋯
+
(
m
−
1
)
N
m
N_{0}=1+N_{2}+2N_{3}+\dots+(m-1)N_{m}
N0=1+N2+2N3+⋯+(m−1)Nm.
2. 二叉树的概念
2.1 二叉树的定义及其主要特性
二叉树是
n
n
n个结点的有限集合:
① 或者为空二叉树,即
n
=
0
n=0
n=0;
② 或者是由一个根结点和两个互不相交的倍称为根的左子树和右子树组成.左子树和右子树又是一棵二叉树.
二叉树与度为2的树区别
二叉树是一种有序树,与度为2的树是不一样的.区别如下所示:
① 度为2的树至少有3个结点,而二叉树是可以为空的;
② 度为2的有序树的孩子结点的左右次序是相对于另外一个结点而言的.二叉树的结点次序不是相对于另外一个结点而言,而是确定的.
几种特殊的二叉树:
- 满二叉树:一棵高度为
h
h
h并含有
2
h
−
1
2^{h}-1
2h−1个结点的二叉树称为满二叉树,即树中的每一层都含有最多的结点.满二叉树的叶子结点都几种在二叉树的最下一层,并且除了叶子结点之外的每个结点的度数均为2.
特别地,如果对满二叉树按照层序进行编号,约定编号从根结点(根结点编号为1)起,自上而下,自左向右.这样每个结点结点对应一个编号,对编号为 i i i的结点,如果有双亲,其双亲为 ⌊ i / 2 ⌋ \lfloor{i/2}\rfloor ⌊i/2⌋,如果有左孩子,那么左孩子的结点编号为 2 i 2i 2i,右孩子的结点编号为 2 i + 1 2i+1 2i+1. - 完全二叉树:设一个高度为
h
h
h,有
n
n
n个结点的二叉树,当且仅当每一个结点都与高度为
h
h
h的满二叉树中编号为
1
∼
n
1\sim{n}
1∼n的结点一一对应的时候,称为完全二叉树.这样的二叉树有以下的一些基本的特点:
① 若 i ≤ ⌊ n / 2 ⌋ i\leq{\lfloor{n/2}\rfloor} i≤⌊n/2⌋,则结点 i i i是分支结点,否则为叶子结点;
② 叶子结点只可能在层次最大的两层上出现.对于最大层次中的叶子结点,都一次排列在该层最左边的位置上;
③ 如果有度为1的结点,只可能有一个,并且该结点只有右孩子或者左孩子;
④ 按照层次顺序编号之后,一旦出现某结点(编号为 i i i)为叶子结点或只有左孩子,则编号大于 i i i的结点均为叶子结点;
⑤ 若 n n n为奇数,则每个分支结点都有左子女和右子女;若 n n n为偶数,则编号最大的分支点(编号为 n / 2 n/2 n/2)只有左子女,没有右子女,其余分支结点左、右子女都有. - 排序二叉树:一棵二叉树或者是空二叉树,或者是具有如下性质的二叉树:左子树上所有结点的关键字均小于根结点的关键字;右子树上的所有结点的关键字均大于根结点的关键字.左子树和右子树又各是一棵排序二叉树.
- 平衡二叉树:树上任一结点的左子树和右子树的深度之差不超过1的排序二叉树.
还有很多不同特点的二叉树,这里就不一一列举了,在后面我们会详细介绍一些重要的二叉树.
二叉树的性质:
- 非空二叉树上叶子结点树等于度为2的结点数加1,即 N 0 = N 2 + 1 N_{0}=N_{2}+1 N0=N2+1;
- 非空二叉树上第 K K K层上至多有 2 k − 1 2^{k-1} 2k−1个结点 ( H ≥ 1 ) (H\geq{1}) (H≥1);
- 高度为 H H H的二叉树至多有 2 H − 1 2^{H}-1 2H−1个结点;
- 对应二叉树按照从上到下、从左到右的顺序依次编号
1
,
2
,
…
,
N
1,2,\dots,N
1,2,…,N,则有以下的关系:
① 当 i > 1 i>1 i>1时候,结点 i i i的双亲结点编号为 ⌊ i / 2 ⌋ \lfloor{i/2}\rfloor ⌊i/2⌋,即当 i i i为偶数的时候,其双亲结点的编号为 i / 2 i/2 i/2,它是双亲的左孩子;当 i i i为奇数的时候,其双亲结点的编号为 ( i − 1 ) / 2 (i-1)/2 (i−1)/2,它是双亲的右孩子;
② 当 2 i ≤ N 2i\leq{N} 2i≤N的时候,结点 i i i的左孩子编号为 2 i 2i 2i,否则没有左孩子;
③ 当 2 i + 1 ≤ N 2i+1\leq{N} 2i+1≤N的时候,结点 i i i的右孩子编号为 2 i + 1 2i+1 2i+1,否则没有右孩子;
③ 结点 i i i所在的层次为 ⌊ log 2 i ⌋ + 1 \lfloor\log_{2}i\rfloor+1 ⌊log2i⌋+1; - 具有 N N N个 N > 0 N>0 N>0结点的完全二叉树的高度为 ⌈ log 2 ( N + 1 ) ⌉ \lceil\log_{2}(N+1)\rceil ⌈log2(N+1)⌉或者为 ⌊ log 2 N ⌋ + 1 \lfloor\log_{2}N\rfloor+1 ⌊log2N⌋+1.
2.2 二叉树的存储结构
二叉树有两种存储方式:顺序存储的方式和链式存储的方式:
顺序存储方式
将完全二叉树上编号为
i
i
i的结点元素存储在某一个下标为
i
−
1
i-1
i−1的分量中,然后通过一些方法确定结点在逻辑上的父子和兄弟关系.
完全二叉树和满二叉树采用顺序存储的方式比较合适,树中结点的序号可以唯一地反映出结点之间的逻辑关系,这样既能最大可能地节省存储空间,又可以利用数组元素下标值确定结点在二叉树中的位置,以及结点之间的关系.
对于一般的二叉树,为了能够显示二叉树中结点的逻辑关系,只能是添加一些并不存在的空结点让其每个结点与完全二叉树上的结点相对照,再存储到一维数组的相应分量中.在最坏的情况下,一个高度为
H
H
H并且只有
H
H
H个结点的单支树却需要占据接近
2
H
−
1
2^{H}-1
2H−1个存储单元.如下图所示是以顺序存储的方式存储一棵二叉树:
链式存储方式
一般的二叉树通常采用链式存储的方式存储.链式结构是指用一个链表来存储一棵二叉树,二叉树中的每一个结点用链表的一个链结点来存储.在二叉树中,结点结构通常包括若干数据域以及若干个指针域.二叉链表至少包含有3个域:数据域data、左指针leftchild和右指针rightchild.
定义的方式如下所示
typedef struct BiTNode{
ElementType data;
struct BiTNode* lchild,rchild;
}
BiTNode,*BiTree;
当然,为了表示同一性,笔者这里使用C++类表示复杂结构的结点结构:
# ifndef BITREENODE_H
# define BITREENODE_H
//二叉树的结点定义方法
template<typename ElementType>
class BiNode{
private:
BiNode<ElementType> *left;
BiNode<ElementType> *right;
ElementType data;
public:
BiNode(){
this->left = NULL;
this->right = NULL;
}
BiNode(ElementType data){
this->left = NULL;
this->right = NULL;
this->data = data;
}
BiNode(ElementType data,BiNode<ElementType>* left,BiNode<ElementType>* right){
this->data = data;
this->left = left;
this->right = right;
}
void SetLeft(BiNode<ElementType>* left){
this->left = left;
}
void SetRight(BiNode<ElementType>* right){
this->right = right;
}
void SetData(ElementType data){
this->data = data;
}
ElementType GetData(){
return this->data;
}
BiNode<ElementType>* GetLeft(){
return this->left;
}
BiNode<ElementType>* GetRight(){
return this->right;
}
~BiNode(){
}
};
# endif //BITREENODE_H
注意:在含有N个结点的二叉链表中含有 N + 1 N+1 N+1个空链域.
小结
本小结主要是讲解一些最为基本的树模型的数据结构。当然有很多种树的结构方式,这篇文章的内容主要是为下一步进行树上的一些计算还有很多遍历的操作提供了基本的定义、特点性质等等。