树的储存结构和表示法
- 前言
树是一类非常重要的数据结构,它是图和其它更高阶数据的基础,人们对树的储存结构和表示法进行了大量研究,这里介绍三种常见的链表结构来表示树的基本方法。
- 树的双亲表示法
假设以一组连续空间储存数据的结点(比如数组或malloc动态分配的空间),同时在每个结点中附设一个指示其双亲结点在链表中的位置的指示器,那么就可以通过结点的遍历,不断双亲,直至遍历完成,为了方便程序设计,约定根节点的双亲序号为‘-1’,如果程序找到双亲为-1的结点,那么就意味着遍历结束。
定义结点结构,双亲结点中包含基本的数据域和父节点位置编号,位置编号定义为parent.
#define MAX_TREE_SIZE 100
typedef struct PTNode
{
TElemType data;
int parent;
}PTNode;
定义树结构,之前提到了用一组连续储存空间来储存结点,为了方便,采用结点结构 的数组来表示树的结构,
//树结构
typedef struct PTree
{
PTNode nodes[MAX_TREE_SIZE];
int r;//r 代表根结点位置
int n; //n 代表结点实际数量,n≤MAX_TREE_SIZE
}
下图展示一棵树的双亲表示以及数据储存结构。
- 孩子表示法
双亲表示法中,求孩子结点时候需要遍历整个树的结构。于是人们发明了孩子表示方法的数据结构。
由于树中每个结点可能有多颗子树,则可用多重链表,也即每个结点有多个指针域,其中每个指针指向一棵树的根节点,此时链表中的结点可以有以下两种格式:
data | child_1 | child_2 | child_3 | child_4 | … | child_d |
---|
如若采用上述结构,所有结点都是同构的,其中d为树的度。由于很多结点的实际子树数量小于d,所以链表中有很多空域,空间较为浪费。
为了节省空间,也可采用另外格式的结点结构,对于每个几点,标明此结点当中实际度数,也就是子孩子的实际数量,勾画出结点结构。
data | degree | child_1 | child_2 | child_3 | … | child_degree |
---|
如若采用此结构,多重链表的结点不同构,导致操作不方便。
除了多重链表,另外一种办法就是把每个结点的孩子结点排列起来,看作一个线性表,则n个结点就有n个线性表。n个头指针(头指针和数据域构成 前置结点)又组成一个线性表,为了便于查找,头指针可采用顺序储存结构。
孩子结点的数据结构采用链表形式,包含孩子所在的位置编号或下标,同时创建next指针,把所有的孩子结点都串起来。
//孩子结点
typedef struct CTNode
{
int child;
struct CTNode *next;
}CTNode, *ChildPtr;
//头结点结构
typedef struct CTBox
{
TElemType data;
ChildPtr firstchild;
}CTBox;
//树结构
typedef struct CTree
{
CTBox nodes[MAX_TREE_SIZE];
int r;//r 代表根结点位置
int n; //n 代表结点实际数量,n≤MAX_TREE_SIZE
}CTree;
左边树的孩子链表表示法,如右图,它本质上是线性表和链表结合,头结点指针构成线性表,孩子结点则采用链表的结构来表示,充分发挥了两类表结构的优势。
与双亲表示法相反,孩子表示法便于实现那些和孩子遍历或访问相关的操作,不适用于向上访问的基本操作。
- 孩子兄弟表示法
此表示法是利用二叉链表结构来表示树,方式比较灵活。链表结点中除了数据域,还包含两个指针域,分别指向结点的第一个孩子结点和下一个兄弟结点,分别命名为firstchild和nextsibling域。
typedef struct CSNode
{
TElemType data;
struct CSNode *firstchild;
struct CSNode *nextsibling;
}CSNode,*CSTree;
若要访问结点x的第i个孩子,则只要先从firstchild域找到第一个孩子结点,然后沿着孩子结点的nextsibling域继续走i-1步,便可以找到x结点的第i个孩子。当然也可以增设一个parent域,方便实现双亲结点的查找和访问等相关工作。
右边的孩子兄弟链表是对左边树的具体表示。
- 小结
本文回顾了树的常见的三种表示方法,并学习了不同表示方法的优缺点,通过回顾,巩固了已经学习的 知识,同时加深了对树的表示的理解。
参考资料:
- 《数据结构》严蔚敏