数据结构 之 树与二叉树(一)

本文介绍了树的基本概念,包括树的定义、基本术语,如根节点、子树、度等,并详细阐述了树的存储结构,包括双亲表示法、孩子表示法(方案一和方案二)以及孩子兄弟表示法,强调了各种表示法的优缺点。

tips:本文仅涉及树的基本内容!!!

1. 树的定义

(Tree)是n(n >= 0)个节点的有限集。n = 0 时称为空树。在任意一颗非空树中:

  1. 有且仅有一个特定的称为根(Root)的结点;
  2. 当n > 1 时,其余结点可分为 m(m > 0) 个互不相交的有限集T1、T2、…、Tm,其中每个集合本身又是一个树,并且称为根的子树(SubTree)。

显然,树的定义是递归的,即在树的定义中有用到了其自身,树是一种递归的数据结构。树作为一种逻辑结构,同时也是一种分层结构,具有以下两个特点:

  1. 树的根结点没有前驱,除根结点外的所有结点有且仅有一个前驱。
  2. 树中所有结点可以有零个或多个后继
    树合适于表示具有层次结构的数据。
    树中的某个结点(除根结点外)最多只和上一层的一个结点(即其双亲结点)有直接关系,根结点没有直接上层关系,因此在n个结点的树中有 n - 1 条边
    而树中每个结点与其下一层的零个或多个结点(其子女结点)有直接关系。

2. 基本术语

树
以结点K为例。

  1. 根结点A到结点K的唯一路径上的任意结点,称为结点K的祖先。如结点B是结点K的祖先,而结点K是结点B的子孙路径上最接近结点K的结点E称为K的双亲,而K为结点E的孩子。根A是树中唯一没有双亲的结点。有相同双亲的结点称为兄弟,如结点K和结点L有相同的双亲E,即K和L为兄弟。
  2. 树中一个结点的孩子个数称为该结点的度,树中结点的最大度数称为树的度。如结点B的度为2,结点D的度为3,树的度为3。
  3. 度大于0的结点称为分支结点(又称为非终端结点);度为0(没有子女结点)的结点称为叶子结点(又称为终端结点)。在分支结点中,每个结点的分支数就是该结点的度。
  4. 结点的层次从树的根结点开始,根结点为第一层,它的子结点为第二层,以此类推。双亲在同一层的结点互为堂兄弟,结点G与E,F,H,I,J 互为堂兄弟。
  5. 结点的深度是从根结点开始自顶向下逐层累加的。
  6. 结点的高度是从叶结点开始自底向上逐层累加的。
  7. 树中结点的各个子树从左到右是有次序的,不能互换,称该树为有序树,否则称为无序树。(上图为有序树)
  8. 树中两个结点之间的路径,是由这两个结点之间所经过的结点序列构成的,而路径长度是路径上所经过的边的个数。
    注意:树中的分支是有向的,即从双亲指向孩子,所以树中的路径是从上向下的,同一双亲的两个孩子之间不存在路径。
  9. 森林是m(m > 0)棵互不相交的树的集合。只要把树的根结点删去就变成了森林。

3.树的性质

  1. 树中的结点数 等于 所有结点的度数之和+1。
  2. 度为 m 的树中第i层上至多有 mi-1 个结点。
  3. 高度为 h 的 m叉树至多有 (mh -1)/(m-1)个结点。
  4. 具有n个结点的m叉树的最小高度为: ⌈ log ⁡ m ( n ( m − 1 ) + 1 ) ⌉ \lceil \log_{m}(n(m-1)+1) \rceil logm(n(m1)+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个头指针又组成一个线性表,采用顺序存储结构,存放进一个一位数组中。
在这里插入图片描述
为此需要设计两种结点结构:
在这里插入图片描述

  1. 孩子链表的孩子结点
    child是数据域,用来存放某个结点在表头数组中的下标;next是指针域,用来存储指向某结点的下一个孩子结点的指针。
  2. 表头数组的表头结点
    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;
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值