一、树的概念
1.树的定义
一个结点x组成的集{x}是一棵树,这个结点x也是这棵树的根。
假设x是一个结点,
T1,T2,…Tk
是K颗互不相交的树,可以构造一颗新树:令x为根,并由K(K
≥
0)条边由x指向树
T1,T2,…Tk
。这些边也叫做分支,
T1,T2,…Tk
称作根为x的树的子树。
- 空集合也是树,称为空树。空树中没有结点。
- 单个结点是一棵树,树根就是结点本身。
- 若某树有多个结点,则每个结点都可以看成是根结点(要么是整个树的根结点,要么是某个子树的根结点)。
在一棵树中,每个结点为其字数的根节点的前驱,而其子树的根结点就是其后继。
- 在一棵树中,有且皆有一个结点没有前驱,这个节点就是树的根结点。
- 除根结点外,其余每个结点有且仅有一个前驱。
- 每个结点可以有任意多个后继。
2.树的相关术语
父结点、子结点、兄弟结点:每个结点的子树的根称为该结点的儿子(子结点),相应地该结点被称为子结点的父亲(父结点),具有同一父结点的结点称为兄弟结点。
路径:如果 n1,n2,nk 是树中的结点序列,并且 ni 是 ni+1(1≤i≤k−1) 的父亲,则结点序列 n1,n2,nk 称为 n1 ~ nk 的一条路径,而且路径长度就是该路径中的结点个数减1.显然,每个结点到自己的路长为0。
祖先与后代:如果从结点A到结点B有一条路径,则称A为B的祖先,B是A的后代。注意,任何一个结点既是它自己的祖先也是它自己的后代。
结点的度:一个结点的子树个数称为该结点的度,或叫做结点的元。
叶结点与分枝结点:度为0的结点,称为叶结点或终结结点,其余的结点叫分枝结点、非叶结点或非终结结点。
树的度:指该树中结点的最大度数。
结点的层:树是一种层次结构,每个结点都处在一定的层次上。从根算起,根为第一层,若某个结点在第L层上,则此结点的儿子在第L+1层。结点的层就是从根到该结点的路长加1。
结点的高:从一个结点的最大路长加1称为该结点的高。
树的高:树的高定义为根节点的高,即该棵树所有结点中最大的层号。
树的深度:一棵树中结点的最大层数称为树的深度。
有序树与无序树:若树中的各结点的子树(兄弟结点)是按一定次序从左向右安排的,则称为有序树,否则为无序树。
森林:森林是m( m≥0 )颗互不相交的树的集合。如果去掉树的根节点,就得到一个森林。
- 结点的顺序
下面把兄弟结点之间的自左而右顺序加以推广。如果a是b的兄弟,且a在b之左,则称a的所有后代在b的所有后代之左。
有一个简单的规则可用来求给结点n其左边与右边的所有结点。即从根到n画一条路径,则这条路径以左的所有结点以及这些结点的所有后代都在n的左边;而这条路径以右的所有结点以及这些结点的所有后代都在n的右边。
3.树的表示
树的表示方法有很多,常用的方法是用括号:先将根节点放入一对圆括号中, 然后把它的子树按照有左至右的顺序放入括号中,而对子树也采用同样的方法处理;同层子树与它的根节点用圆括号括起来,同层子树之间用逗号隔开,最后用闭括号括起来。
如上图的树可表示为(A(B(E((K),(L)),(F)),C(G),D(H(M)),(I),(J))))。
详情参见树的表示
二、二叉树
1.二叉树的概念
在树的应用中,二叉树是最简单的一种形式,而二叉树又特别重要,这是因为处理树的许多算法应用到二叉树时变得非常简单,而任意的树又可以方便地转换成对应的二叉树。因此,只需要定义二叉树的算法,容纳后将其他树转换为二叉树,即可方便地对所有的树进行操作。所以说,二叉树是所有书结构的基础。
- 二叉树
二叉树是有限个结点的集合,这个集合或者是空集,或者由一个根结点和两颗不想叫的二叉树组成, 其中的一棵叫做根的左子树,另一颗叫做根的右子树。
注意:虽然树和二叉树之间有许多联系,但是二叉树不是作为树的特殊情形来定义的。
二叉树有5种基本形态:
- 空
- 仅有根节点
- 仅有左子树
- 仅有右子树
- 有左右子树
二叉树和树的两个主要差别如下:
- 树中结点的最大度数没有限制,而二叉树结点的最大度数为2。
- 树的结点无左、右之分,而二叉树的结点有左、右之分。
满二叉树
满二叉树是高为k且有 2k−1 个结点的二叉树。完全二叉树
完全二叉树是具有下属性质的二叉树(设二叉树的高为k):
- 所有叶结点都出现在k或k-1层。
- k-1层的所有叶结点都在非终结结点的右边。
- 除k-1层的最右非终结结点可能有一个(只能是左分支)或两个分支之外,其余非终结结点都有两个分支。
注:满二叉树肯定是完全二叉树,而完全二叉树不一定是满二叉树。
2.二叉树的性质
根据二叉树的定义,可得知其具有一下性质:
- 在二叉树中,第i层的结点总数最多为2i-1。
- 深度为k的二叉树最多有2k-1个结点( k≥1 ),最少有k个结点。
- 对于一颗二叉树,如果其叶结点数为 n0 ,而度为2的结点总数为 n2 ,则 n0=n2+1 。
- 具有n个结点的完全二叉树的深度k为: k=[log2n]+1 .
- 有n个结点的完全二叉树各结点如果用顺序方式存储,对任意结点i,有如下关系:
- 如果i!=1,则其父结点的编号为i/2。
- 如果2*i ≤ n,则其左子树根节点的编号为2*i;若2*i ≥ n,则无左子树;
- 如果2*i+1 ≤ n,则其右子树根节点编号为2*i+1;若2*i+1 ≥ n,则无右子树。
3.二叉树的存储
二叉树通常可采用三类存储结构:顺序存储结构、链式存储结构和游标表示结构。
- 顺序存储结构
与线性表的顺序存储类似,二叉树的顺序存储结构一般也有一个一维数组来构成,二叉树上的结点按照某种贵的次序逐个保存到数组的各个单元中。
二叉树顺序存储结构的数据定义如下:
define MAXSIZE 100 //最大结点数
typedef int DATA; //元素类型
tpedef int DATA SeqBinTree[MAXSIZE];
SeqBinTree SBT; //定义保存二叉树数组
注:要使用一维数组保存二叉树的结点, 关键在于定义结点的存储次序,要求这种次序应该反映结点之间的逻辑关系(父子关系),否则二叉树的基本运算就难以实现。
对于非完全二叉树为了能使结点与数组序号有对应的关系,一般采取的方法是:将非完全二叉树转换为完全二叉树,将左侧缺少的结点虚设为无数据的结点(这里假设以符号“#”来表示)。
将上图所示的完全二叉树按顺序保存到一维数组中,得到如下图所示的一位数组,这样每个结点的序号又与数组的序号有了对应关系。
- 链式存储结构
由于二叉树的顺序存储结构会造成存储空间的浪费,因此大多数情况下二叉树都采用链式存储结构。使用链式存储接哦股非常符合二叉树的逻辑结构,一个结点可以由节点元素和两个分别指向左、右子树的指针组成,这种节点结构称为二叉链表结构。有时为了方便查找父节点,还可增加一个指向父结点的指针,这种结构称为三叉链表结构。
二叉链表结构:
typedef struct ChainTree
{
DATA data;//元素数据
struct ChainTree *left;//左子树结点指针
struct ChainTree *right;//右子树结点指针
}ChainTreeType;
ChainTreeType *root=NULL;//定义二叉树根节点指针
三叉链表结构:
typedef struct ChainTree
{
DATA data;//元素数据
struct ChainTree *left;左子树结点指针
struct ChainTree *right;//右子树结点指针
struct ChainTree *parent;//父结点指针
}ChainTreeType;
ChainTreeType *root=NULL;//定义二叉树根节点指针
4.二叉树的实现
源码参见二叉树的实现。
5.二叉树的遍历
遍历是对树的一种基本运算,所谓遍历二叉树,就是按照一定的规则和顺序走遍二叉树的所有子树。使每一个结点都被访问一次,而且只被访问一次。
对于具有左右子树的二叉树,其中D表示根结点,L表示左子树,R表示右子树,对其遍历有三种方式:
- 按层遍历:对于二叉树的按层遍历,不能方便地使用递归算法来编写代码,而是使用一个循环队列来进行处理。首先将第1层(根节点)进入队列,再将第1结点根结的左右子树(第2层)进入队列,……就这样循环处理,就可以逐层遍历。
先序遍历(DLR):称为先根次序遍历,即先访问根节点,再按先序遍历左子树,最后按先序遍历右子树。
中序遍历(LDR):称为中根次序遍历,即先按中序遍历左子树,再访问根节点,最后按中序遍历右子树。
后序遍历(LRD):称为后根次数遍历,即先按后序遍历左子树,再按后序遍历右子树,最后再访问根节点。
源码参见二叉树的遍历
6.线索二叉树
详情参见线索二叉树
三、二叉树的应用
1.堆及其实现
详情参见堆及其实现
2.遍历二叉树的应用
详情参见遍历二叉树的应用
四、树、森林与二叉树的关系
详情参见树、森林与二叉树的转换与应用