一、课程目标
了解树的三种存储结构,能够编码实现树的存储以及访问等操作。
二、目标详解
说到存储结构,无外乎顺序存储以及链式存储两种。就顺序存储而言,在内存中用一段连续的存储单元依次存储线性表的数据元素,如数组、栈、队列等。对于线性数据结构来说,顺序存储是很自然的,但是对于树这种一对多的结构呢?
树中某个结点的孩子可以有多个,这就意味着,无论按照何种顺序将树中所有结点存储到数组中,结点的存储位置都无法直接反映逻辑关系。如果用顺序结构,数据元素挨个的存储,那么谁是谁的双亲谁是谁的孩子呢?因此简单的顺序存储结构是不能满足树的实现要求的。
对于树的存储,要充分利用顺序存储和链式存储的特点,我们这里介绍三种常用的邻接表的存储方法:双亲表示法、孩子表示法、双亲孩子表示法。
下图为要存储的示例树的逻辑结构:
1. 双亲表示法
在树中,结点可能会没有孩子,但是除了根节点外,每个结点都会有一个确定的双亲。我们假设以一组连续空间存储树的结点,同时在每个节点中,附设一个指示器指出其双亲结点在链表中的位置,也就是说,每个节点除了知道自己是谁以外,还知道它的双亲在哪里。各结点的结构如下所示:
data | parent |
---|---|
数据域:结点数据信息 | 指针域:存储双亲结点位置 |
代码实现:
#define Max_Node_Num 100 // 结点最大个数
struct Node // 结点结构
{
char data; // 数据域,类型可变
int parent; // 指针域,双亲位置
};
struct Tree // 树结构
{
Node nodes[Max_Node_Num]; // 结点数组
int root; // 根节点位置
int num; // 结点数
};
下图为示例树的双亲表示法的存储结构:
双亲表示法的特点:
- 由于根节点是没有双亲的,一般约定根节点的指针域值为-1。
- 根据结点的parent指针,可以很容易的找到它的双亲结点。所用时间复杂度为O(1),知道parent值为-1时,表示找到了树的根。
- 如果想要找到孩子结点,需要遍历整个结构才行。
2. 孩子表示法
把每个结点的孩子排列起来,以单链表作为存储结构,则n个结点有n个孩子链表,如果是叶子结点则此链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中,结点结构如下:
- 表头数组的表头结点
data(数据域) | firstchild(头指针域) |
---|---|
存储某个结点的数据信息 | 存储该节点的孩子链表的头指针 |
- 孩子链表的孩子结点
child(数据域) | next(指针域) |
---|---|
存储某个结点在表头数组的下标 | 存储指向某节点的下一个孩子结点的指针 |
代码实现:
#define Max_Node_Num 100 // 结点最大个数
struct Child // 孩子结点
{
int child; // 孩子结点的下标
Child* next; // 下一个孩子结点的指针
};
struct Box // 表头结构
{
char data; // 数据域:存放各结点数据
Child* firstchild; // 指向第一个孩子结点的指针
};
struct Tree // 树结构
{
Box nodes[Max_Node_Num]; // 结点数组
int root; // 根节点位置
int num; // 结点数
};
下图为示例树的孩子表示法的存储结构:
对于孩子表示法,查找某个结点的某个孩子,或者找某个结点的兄弟,只需要查找这个结点的孩子单链表即可。但是当要寻找某个结点的双亲时,就不是那么方便了。所以可以将双亲表示法和孩子表示法相结合,形成双亲孩子表示法。
3. 双亲孩子表示法
在孩子表示法的表头结构中添加存储结点双亲位置的指针,变为如下结构:
data | parent | firstchild |
---|---|---|
存储某个结点的数据信息 | 存储双亲结点位置 | 存储该节点的孩子链表的头指针 |
代码实现:
#define Max_Node_Num 100 // 结点最大个数
struct Child // 孩子结点
{
int child; // 孩子结点的下标
Child* next; // 下一个孩子结点的指针
};
struct Box // 表头结构
{
char data; // 数据域:存放各结点数据
int parent; // 存放双亲结点的位置
Child* firstchild; // 指向第一个孩子结点的指针
};
struct Tree // 树结构
{
Box nodes[Max_Node_Num]; // 结点数组
int root; // 根节点位置
int num; // 结点数
};
下图为示例树的双亲孩子表示法的存储结构:
4. 孩子兄弟表示法
各节点的指针域存放子节点和兄弟节点的指针。
示例树的孩子兄弟表示法的存储结构:
三、扩展
由于树可以看成是一张无向图,在信奥赛中,涉及到树的题目时,常用邻接矩阵的形式存储树的结构,若树的结点数为n,则设立一个n*n的二维数组a,在数组a中,若a[i][j]的值为0则表示i结点不是j结点的双亲结点(没有边相连接);若a[i][j]值为1则表示i号结点是j号结点的双亲结点(有边相连接),这样也能存储下来一颗树,但是在树的结点较多时,二维数组中存在大量的无效数据,空间浪费较多,此时应使用领接表的存储方式。