tips:本文仅涉及树的基本内容!!!
树
1. 树的定义
树(Tree)是n(n >= 0)个节点的有限集。n = 0 时称为空树。在任意一颗非空树中:
- 有且仅有一个特定的称为根(Root)的结点;
- 当n > 1 时,其余结点可分为 m(m > 0) 个互不相交的有限集T1、T2、…、Tm,其中每个集合本身又是一个树,并且称为根的子树(SubTree)。
显然,树的定义是递归的,即在树的定义中有用到了其自身,树是一种递归的数据结构。树作为一种逻辑结构,同时也是一种分层结构,具有以下两个特点:
- 树的根结点没有前驱,除根结点外的所有结点有且仅有一个前驱。
- 树中所有结点可以有零个或多个后继。
树合适于表示具有层次结构的数据。
树中的某个结点(除根结点外)最多只和上一层的一个结点(即其双亲结点)有直接关系,根结点没有直接上层关系,因此在n个结点的树中有 n - 1 条边。
而树中每个结点与其下一层的零个或多个结点(其子女结点)有直接关系。
2. 基本术语

以结点K为例。
- 根结点A到结点K的唯一路径上的任意结点,称为结点K的祖先。如结点B是结点K的祖先,而结点K是结点B的子孙。路径上最接近结点K的结点E称为K的双亲,而K为结点E的孩子。根A是树中唯一没有双亲的结点。有相同双亲的结点称为兄弟,如结点K和结点L有相同的双亲E,即K和L为兄弟。
- 树中一个结点的孩子个数称为该结点的度,树中结点的最大度数称为树的度。如结点B的度为2,结点D的度为3,树的度为3。
- 度大于0的结点称为分支结点(又称为非终端结点);度为0(没有子女结点)的结点称为叶子结点(又称为终端结点)。在分支结点中,每个结点的分支数就是该结点的度。
- 结点的层次从树的根结点开始,根结点为第一层,它的子结点为第二层,以此类推。双亲在同一层的结点互为堂兄弟,结点G与E,F,H,I,J 互为堂兄弟。
- 结点的深度是从根结点开始自顶向下逐层累加的。
- 结点的高度是从叶结点开始自底向上逐层累加的。
- 树中结点的各个子树从左到右是有次序的,不能互换,称该树为有序树,否则称为无序树。(上图为有序树)
- 树中两个结点之间的路径,是由这两个结点之间所经过的结点序列构成的,而路径长度是路径上所经过的边的个数。
注意:树中的分支是有向的,即从双亲指向孩子,所以树中的路径是从上向下的,同一双亲的两个孩子之间不存在路径。 - 森林是m(m > 0)棵互不相交的树的集合。只要把树的根结点删去就变成了森林。
3.树的性质
- 树中的结点数 等于 所有结点的度数之和+1。
- 度为 m 的树中第i层上至多有 mi-1 个结点。
- 高度为 h 的 m叉树至多有 (mh -1)/(m-1)个结点。
- 具有n个结点的m叉树的最小高度为: ⌈ log m ( n ( m − 1 ) + 1 ) ⌉ \lceil \log_{m}(n(m-1)+1) \rceil ⌈logm(n(m−1)+1)⌉
4. 树的存储结构
利用顺序存储结构和链式存储结构的特点,实现对树的存储结构的表示。
4.1 双亲表示法
除了根结点外,每个结点一定有且仅有一个双亲。
假设以一组连续空间存储树的结点,同时在每个结点中,附设一个指示器指示其双亲结点在数组中的位置。

data是数据域,存储结点的数据信息;parent是指针域,存储该结点的双亲在数组中的下标。
//双亲表示法结点的结构定义
#define MAX_TREE_SIZE 100
typedef int TElemType;
typedef struct PTNode //结点结构
{
TElemType data;
int parent; //双亲位置
}PTNode;
typedef struct //树结构
{
PTNode nodes[MAX_TREE_SIZE]; //结点数组
int r,n; //根的位置和结点数
}PTNode;
由于根结点没有双亲,所有我们约定根结点的位置域设置为-1。
示例:

上图中的树结构 的双亲表示法 如下:

如此,根据结点的parent指针可以很容易的找到它的双亲结点,所用的时间复杂度为O(1), 但是如果要知道结点的孩子是什么,则需要遍历整个结构才行。
对此,可以在结点中再增加一个结点最左边孩子的域,称之为长子域,如果没有孩子结点就设为-1。如图:


同样,如果还想知道结点的兄弟,则再增加一个右兄弟域,存放结点的右兄弟在数组中的下标,如果结点没有右兄弟,则为-1.


4.2 孩子表示法
由于树中的每个结点可能有多颗子树,可以考虑多重链表,即每个结点有多个指针域,其中每个指针指向一颗子树的根结点,把这种方式叫做多重链表表示法。
4.2.1 方案一
指针域的个数 等于 树的度。树的度是树中各个结点度的最大值。

data是数据域;child1~childend 是指针域,用来指向该结点的孩子结点。
示例:

树的度为3,所以指针域的个数是3。

这种方法对树中各结点的度相差很大时,是很浪费空间的。
4.2.2 方案二
每个结点指针域的个数等于该结点的度,专门取一个位置来存储结点指针域的个数。
其结构如下:


这种方法克服了浪费空间的缺点,对空间的利用率是很高了,但由于各个结点的链表是不相同的结构,加上要维护结点的度的数值,在运算上就会带来时间上的损耗。
可以把每个结点放到一个顺序存储结构的数组中,再对每个结点的孩子建立一个单链表体现它们的关系。
这就是孩子表示法,即把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一位数组中。

为此需要设计两种结点结构:

- 孩子链表的孩子结点
child是数据域,用来存放某个结点在表头数组中的下标;next是指针域,用来存储指向某结点的下一个孩子结点的指针。 - 表头数组的表头结点
data是数据域,存储某结点的数据信息;firstchild是头指针域,存储该结点的孩子链表的头指针。
以下是孩子表示法的结构定义代码:
//树的孩子表示法结构
#define MAX_TREE_SIZE 100
typedef int TElemType;
typedef struct CTNode //孩子结点
{
int childl
struct CTNode* next;
}*ChildPtr;
typedef struct //表头结构
{
TElemType data;
ChildPtr firstchild;
}CTBox;
typedef struct //树结构
{
CTBox nodes[MAX_TREE_SIZE]; //结点数组
int r,n; //根的位置和结点数
}CTree;
4.3 孩子兄弟表示法
任意一颗树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
结点结构如下:

data是数据域,firstchild是指针域,存储该结点的第一个孩子结点的存储结构地址;rightsib是指针域,存储该结点的右兄弟结点的存储地址。
结构定义代码如下:
//树的孩子兄弟表示法结构定义
typedef struct CSNode
{
TElemType data;
struct CSNode* firstchile, * rightsib;
}CSNode,* CSTree;
本文介绍了树的基本概念,包括树的定义、基本术语,如根节点、子树、度等,并详细阐述了树的存储结构,包括双亲表示法、孩子表示法(方案一和方案二)以及孩子兄弟表示法,强调了各种表示法的优缺点。
710

被折叠的 条评论
为什么被折叠?



