【数据结构】树(一)—— 树的基础知识(C语言版)

一、树的定义

树(Tree)是 n(n≥0)个结点的有限集。

n=0 时称为空树。

在任意一颗非空树中:
(1)有且仅有一个特定的称为根(Root)的结点

(2)当 n>1时,其余结点可分为 m(m>0)个互不相交的有限集 T1、T2、…、Tm, 其中每一个集合本身又是一棵树,并且称为根的子树(SubTree),如下图所示

在这里插入图片描述

树的其它表示方法

在这里插入图片描述

其中(a)是以嵌套集合 (即是一些集合的集体,对于其中任何两个集合,或者不相交,或者一个包含另一个) 的形式表示 的; (b)是以广义表的形式表示的,根作为由子树森林组成的 表的名字写在表的左边; © 用的 是凹入表示法(类似书的编目)。

树的定义其实实质是递归的方法。如下图,子树T1 和 T2 就是根节点A的子树

在这里插入图片描述

注意

n>0 时,根节点唯一,不存在多个根节点
m>0 时,子树个数没有限制,但**一定互不相交**

如下图:不属于树。
在这里插入图片描述

二、树的相关概念

树的结点包含一个数据元素及若干指向其子树的分支。

1. 度(Degree)

度(Degree):结点拥有的子树树称为结点的度。
(1)度为0的结点称为 叶节点(Leaf)或终端结点。
(2)度不为0的结点称为非终端结点或分支结点。
(3)除根节点外,分支结点也称为内部结点。
(4)树的度就是树内各结点的度的最大值。

在这里插入图片描述

2. 结点间的关系

结点的子树的根称为该结点的 孩子(Child), 相应的,该结点称为汉子的双亲(Parent)。
同一个双亲的孩子之间互称 兄弟(Sibling)。
结点的祖先是从根到该结点所经分支上的所有结点。
以某结点为根的子树中的任一结点都称为该结点的 子孙。

在这里插入图片描述

3. 结点的层次(Level)

结点的 层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。树中结点的最大层次称为树的 深度(Depth)或高度。比如下图树的深度为4

在这里插入图片描述

4. 有序树与森林

有序树:树中结点的各子树从左至右有次序,不能互换,否则为 无序树。

森林(Forest):是 m(m>0)棵互不相交的树的集合。
对树中每个结点而言,其子树的集合即为森林

三、线性表和树的不同

在这里插入图片描述

四、树的性质

  • 树中的结点数等于所有结点的度数加1
  • 度为 m 的树中第 i 层上至多有 m i − 1 m^{i-1} mi1个结点(i大于等于1)
  • 高度为 h 的 m 叉树至少有 h个结点,至多有 ( m h − 1 ) / ( m − 1 ) (m^h-1)/(m-1) (mh1)/(m1) 个结点
  • 具有 n 个结点的 m 叉树的最小高度为

在这里插入图片描述

  • m 叉树允许所有结点的度都小于m,任意结点的度最多有m个孩子,且可以是空树;
  • 度为 m 的树,任意结点的度小于等于 m ,至少有一个结点的度等于 m,且一定是非空树,至少有 m+1个结点。

五、树的抽象数据类型

ADT 树 (tree)
Data
    树是由一个根节点和若干棵子树构成。树中结点具有相同数据类型及层次关系
Operation
    InitTree(*T): 构造空树 T
    DestroyTree(*T):销毁树 T
    CreateTree(*T, definition):按 definitin 中给出树的定义来构造树
    ClearTree(*T):若树 T 存在, 则将树 T清为空树
    TreeEmpty(T): 若 T 为空树,返回 true, 否则返回 false。
    TreeDepth(T): 返回 T 的深度
    Root(T):返回 T 的根节点
    Value(T, cur_e):cur_e 是树 T 中一个结点,返回此节点的值
    Assign(T, cur_e, value):给树 T 的结点 cur_e 赋值为 value
    Parent(T, cur_e):若 cur_e 是树 T 的非根节点,则返回它的双亲,否则返回空
    LeftChild(T, cur_e):若 cur_e 是树 T 的非叶节点,则返回它的最左孩子,否则返回空
    RightSibling(T, cur_e):若 cur_e 有右兄弟,则返回它的右兄弟,否则返回空
    InsertChild(*T, *p, i, c):其中p 指向树 T 的某个结点,i (1<= i <= p)为所指结点 p 的度加上 1, 非空树 c 与 T不相交.
    操作结果为插入 c 为树 T 中 p 所指结点的第 i 课子树
    DeleteChild(*T, *p, i):其中 p 指向树 T 的某个节点,i (1<= i <= p)为所指结点 p 的度.
    操作结果为删除 T 中  p 所指结点的第 i 棵子树
endADT

六、树的存储结构

先分别说下三种不同的表示法:双亲表示法、孩子表示法、孩子兄弟表示法

双亲表示法:一般采用顺序存储方式;
孩子表示法:采用顺序存储+链式存储;
孩子兄弟表示法:采用链式存储,(其实是一个二叉链表,相当于构建二叉树)

1. 双亲表示法

树的结点以一组连续空间表示,且在每个结点中,附设一个指示器指示其双亲结点到链表中的位置。(静态链表)

在这里插入图片描述
其中data是数据域,存储结点的数据信息。而parent是指针域,存储该结点的双亲在数组中的下标

/* 树的双亲表示法结点结构定义 */
typedef int TElemType;  // 树结点的数据类型,目前暂定为整形
typedef struct PTNode   // 结点结构
{
    TElemType data;     // 结点数据
    int parent;         // 双亲位置
}PTNode;

typedef struct          // 树结构
{
    PTNode nodes[MAX_TREE_SIZE];  // 结点数组
    int r, n;  // 根的位置和结点数
}PTree;

在这里插入图片描述
根节点没有双亲,故根节点的域设置为 -1,双亲表示法根据parent指针很容易找到它的双亲结点,时间复杂度为O(1)。

双亲表示法的优化

双亲表示法对于双亲很容易找到,但是,节点的孩子不太好找,需要遍历整个结构,所以稍加改动,添加一个左孩子域就行,没有左孩子的就设为-1

在这里插入图片描述

同样对于兄弟节点比较关注的话,添加一个兄弟节点域。

在这里插入图片描述

如果节点的孩子还很多,超过2个。然后又关注节点的双亲、又关注节点的孩子、还关注节点的兄弟,而且对时间的遍历要求还比较高,那么我们还可以把此结构扩展为有双亲域、长子域、再有右兄弟域。存储结构的设计是一个非常灵活的过程。一个存储过程设计得是否合理,取决于基于该存储结构的运算是否合适、是否方便,时间复杂度好不好等。注意也不是越多越好,又需要时再设计相应的结构。

2. 孩子表示法

多重链表,就是链表里的结点可能隶属多个链表,
https://blog.csdn.net/maxiaoyin111111/article/details/84542585

思路: 由于树中每个结点可能有多棵子树,可以考虑用多重链表,即每个结点有多个指针域,其中每个指针指向一棵子树的结点。(多重链表表示法)

缺点: 对于树的每个结点的度,即它的孩子的个数是不同的。
解决方案:
(一) 指针域的个数等于树的度

在这里插入图片描述

在这里插入图片描述此方法容易造成内存空间的浪费。

(二)每个结点指针域的个数等于该结点的度。
使用一个特定位置存储结点指针域的个数

在这里插入图片描述其中 data 为数据域, degree 为度域, 也就是存储该结点的孩子结点的个数, child1 到 child 为指针域, 指向该结点的各个孩子的结点。

在这里插入图片描述这种方法克服了浪费空间的缺点,对空间利用率很高,但是由于各个结点的链表是不相同的结构,加上要维护结点的度的数值,在运算上会带来时间上的损耗。

两方法的更进一步优化:
为了遍历整课树,把每个结点放到一个顺序存储结构的数组中是合理的,但是每个结点的孩子有多少是不确定的,所以可以建立一个单链表体现它们的关系。即孩子表示法

孩子表示法:由孩子链表的孩子结点和表头数组的表头结点组合。

把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点有n个孩子链表,如果是叶子节点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,采用个顺序存储结构,存放进一个一维数组中。

在这里插入图片描述

为此,有两种结点结构: 孩子链表的孩子结点 , 表头数组的表头结点

在这里插入图片描述
孩子链表的孩子结点, child为数据域,存储某个结点在表头数组中的下标,next为指针域,存储指向下一孩子结点的指针

在这里插入图片描述
表头数组的表头结点,data是数据域,存储某结点的数据信息。firstchild是头指针域,存储该结点的孩子链表的头指针。

/* 树的孩子表示法结构定义 */
#define MAX_TREE_SIZE 100
typedef struct CTNode  // 孩子结点
{
    int child;
    struct CTNode *next;
}*ChildPtr;
typedef struct  // 表头结构
{
    TElemType data;
    ChildPtr firstchild;
}CTBox;
typedef struct  // 树结构
{
    CTBox nodes[MAX_TREE_SIZE];  //结点数组
    int r, n;                    // 根的位置和结点数
}CTree;

优点:方便查找孩子以及兄弟结点
缺点:对于双亲则需要遍历整棵树。
优化方法:添加双亲域

在这里插入图片描述

3. 孩子兄弟表示法

任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。故可设置两个指针,分别指向第一个孩子和此结点的右兄弟。

孩子兄弟表示法
结点结构中,data是数据域,firstchild是指针域,存储该结点的第一个孩子结点的存储地址,rightsib是指针域,存储该结点的右兄弟结点的存储地址

在这里插入图片描述

/* 树的孩子兄弟表示法结构定义 */
typedef struct CSNode
{
    TElemType data;
    struct CSNode *firstchild, *rightsib;
}CSNode, *CSTree;`

这种表示法的好处就是将一颗复杂的树变成了一颗二叉树了。
在这里插入图片描述
麻烦的是找双亲不太好找,需要的话可以加个parent指针来解决快速查找双亲的问题。

本篇文章摘自《大话数据结构》,有兴趣的小伙伴可以找这本书来看看。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何为xl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值