树-树及二叉树的定义和存储结构、遍历二叉树、线索二叉树、哈夫曼树及其应用(C语言描述)

此文章仅作为自己学习过程中的记录和总结,同时会有意地去用英文来做笔记,一些术语的英译不太准确,内容如有错漏也请多指教,谢谢!


一、树(Tree)的定义:

  • 树(Tree):是n(n>=0)个结点的有限集。n=0时称为空树。不同于先前所学的一对一的线性结构,树是一种一对多的数据结构。在任意一棵非空树中:(1):有且仅有一个特定的称为根(Root)的结点。根结点是唯一的,不可能存在多个根结点;(2):当n>1时,其余结点可以分为m个(m>0)互不相交的有限集T1、T2…Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。子树的个数没有限制,但它们一定是互不相交的。

树的定义其实蕴含了递归的思想,即在树的定义中还用到了树的概念。

在这里插入图片描述

- 树的其它相关概念:

  • 树的结点 包含一个数据元素以及若干指向其子树的分支。
  • 结点拥有的子树数称为 结点的度(Degree)。一棵树的度 是指该树内各结点的度的最大值。
  • 结点的层次(Level) 从根开始定义起。根为第一层,根的孩子为第二层。一棵树的深度(Depth)或高度 是指该树中结点的最大层次。
  • 若将树中结点的各子树看成从左到右是有次序、不能互换的,则称该树为有序树,否则称为无序树。
  • 森林(Forest) 是指m(m >= 0)棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。

- 结点分类:

  • 度为0的结点称为 叶结点(Leaf) 或终端结点。
  • 度不为0的结点称为分支结点或非终端结点。
  • 除了根结点之外,分支结点也称作内部结点。

- 结点间关系:

  • 某结点的子树的根称为该结点的 孩子(Child),相应地,该结点称为孩子的 双亲(Parent)。(注意:双亲结点是指一个结点而不是两个。)
  • 同一个双亲的孩子之间互称 兄弟(Sibling)。 双亲在同一层的结点互为堂兄弟。
  • 某结点的祖先是从根到该结点所经过分支上的所有结点。反之,以某结点为根的子树中的任一结点都称为该结点的子孙。

- 线性结构与树结构的比较:

· 线性结构:

  1. 第一个数据元素:无前驱。
  2. 最后一个数据元素:无后驱。
  3. 中间元素:有一个前驱一个后驱。

· 树结构:

  1. 根节点:无双亲,是唯一的。
  2. 叶结点:无孩子,可以有多个。
  3. 中间结点:有一个双亲,有一或两个孩子。

二、树的抽象数据类型:

相对于线性结构,树的操作就完全不同了。

Data: 树是由一个根节点和若干棵子树构成。树中结点具有相同数据类型及层次关系。

[ADT of trees:]

Operations :
	InitTree(*T):构造空树T。
	DestroyTree(*T):销毁树T。
	CreateTree(*T, definition):按照definition中给出的树的定义来构造树T。
	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的非根结点,则返回它的双亲,否则返回NULLLeftChild(T, cur_e):若cur_e是树T的非叶结点,则返回它的最左孩子,否则返回NULLRightSibling(T, cur_e):若cur_e有右兄弟,则返回它的右兄弟,否则返回NULLInsertChild(*T, *p, i, c):其中p指向树T的某个结点,i为所指结点p的度 + 1,非空树c与树T不相交。操作结果为:插入非空树c作为树T中p所指结点的第i棵子树。
	DeleteChild(*T, *p, i):其中p指向树T的某个结点,i为所指结点p的度。操作结果为:删除T中p所指结点的第i棵子树。

endADT

三、树的存储结构:

对于树这种一对多的数据结构,无论按何种顺序将树中所有结点存储到数组中,结点的存储位置都无法直接反映逻辑关系,因此简单的顺序存储结构是不能满足树的实现要求的。不过,若果能充分利用顺序存储和链式存储结构的特点,就完全可以实现树的存储结构的表示。此处介绍三种不同的表示法:①双亲表示法、②孩子表示法、③孩子兄弟表示法。


  • ① 双亲表示法:

因为除了根结点之外,其余每个结点不一定有孩子但一定有且仅有一个双亲,所以我们可以假设以一组连续的空间来存储树的结点,同时在每个结点中附设一个指示器指示其双亲结点在数组中的位置。也就是说每个结点除了知道自己是谁之外,还知道自己的双亲的位置。

此时的结点结构包含两个域:① “data” 是数据域,用来存储结点的数据信息;② “parent” 指针域,用来存放双亲在数组中的下标。(由于根结点没有双亲,我们约定根结点的 parent 设置为-1。因此此时所有结点都存有了其双亲的位置。)

结点结构如图所示:
在这里插入图片描述用双亲表示法表示树结构如下图:
在这里插入图片描述

双亲表示法的结点结构定义代码如下:

//树的双亲表示法结点结构定义。
#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; //记录根的位置"r"和结点数"n"。
} PTree;

但是,虽然这样的存储结构可以让我们很方便地找到结点的双亲( T(n)=O(1) ),可当我们要知道结点的孩子是什么,却需要遍历整个结构才行。

于是为了改进这个存储结构,我们可以给结点增加一个域 “firstchild” 来存放结点最左边孩子的位置下标,我们称之为“长子域”。有了长子域我们就可以很容易得到结点孩子的位置。 如果没有孩子,firstchild域就设置为 -1。

改进效果如下图:
在这里插入图片描述
此时我们解决了对于有0或1个孩子的结点需要找孩子的问题了,但是对于有多个孩子的结点,如果其需要关注兄弟之间的关系该如何处理呢?

同样地,我们可以再增加一个右兄弟域 “righsib” 来体现兄弟关系。 也就是说,每一个结点如果它存在右兄弟,则记录右兄弟的下标。如果没有右兄弟,rightsib域就设置为 -1。

改进效果如下图:
在这里插入图片描述


  • ② 孩子表示法:

由于树中每个结点可能有多棵子树,可以考虑用完全不同于双亲表示法思路的多重链表这种数据结构,即每个结点有多个指针域,其中每个指针指向一棵子树的根结点,我们称这种方法叫做多重链表表示法。但是这种方法也有缺陷:树的每个结点的度,即它们的孩子数是不一定相同的。为此,我们有两种方案去解决这个缺陷。

  1. 如下图所示,令每个结点的指针域的个数就等于树的度。但是,这种方法对于树中各结点的度相差很大时,是十分浪费空间的。
    在这里插入图片描述

  2. 思路是按需分配空间。如下图所示,令每个结点指针域的个数等于该结点的度,我们专门取一个位置 “degree” 来存储结点指针域的个数。这种方法克服了方案一浪费空间的缺点,空间利用率显著提升。但是由于各个结点的链表是不尽相同的结构,加上要维护结点的度的数值,在运算上就会带来时间上的损耗。
    在这里插入图片描述

既然两种方案都有其缺陷,那么,能否有一个方法既能减少空指针的浪费又能使结点结构相同呢?

仔细观察,可以发现为了要遍历整棵树,把整个结点放到一个顺序存储结构的数组中是合理的。但由于每个结点的孩子有多少是不确定的,因此我们再对每个结点的孩子建立一个单链表来体现它们的关系。这就是我们要讲的孩子表示法


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

为此,设计两种结点结构。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值