文章目录
前言
在学了二叉树,这种特殊的树后,我们再回到一般的树,讨论他的存储和遍历
一、树的存储
实际在写代码的时候,人们用到很多种办法来存储树,以下我来介绍最常见的三种方法
1.双亲表示法
因为我们知道树中的每一个节点都有唯一的双亲,我们正是利用这个特点来通过一个顺序表存储树中的节点,每个节点中多加一个元素来存储其双亲的节点,节点的结构如下:
data | parent |
---|
存储结构定义为下:
typedef struct TNode{
DataType data;
int parent;
}TNode;
typedef struct
{
TNode tree[MAX];
int root;
int num;
}PTree;
顺序表的结构为:
Data | Parent | |
---|---|---|
0 | A | -1 |
1 | B | 0 |
2 | C | 0 |
3 | D | 0 |
4 | E | 1 |
5 | F | 1 |
6 | G | 3 |
7 | H | 3 |
8 | I | 3 |
9 | J | 6 |
因为每一个节点都包含着他的双亲的信息,所以用这个结构在反复求节点的双亲的时候很方便,但是求一个节点的孩子节点的时候,就比较麻烦,必须遍历整个表来找到节点的双亲值是当前的节点。
2.孩子表示法
孩子表示法,就是把每个节点的孩子存到一个单链表中去,这个链表称为“孩子链表”,每一个节点都对应一个孩子链表,没有孩子的节点,对应的孩子链表为空,节点的数据和孩子的头指针,我们还是用一个顺序表来存储。
存储结构定义为下:
typedef struct ChildNode{
//孩子链表节点结构的定义
int Child;
struct ChildNode *next;
}ChildNode;
typedef struct {
DataType data;
ChildNode * FirstChild;//用来存储孩子链表的头结点
}DataNode;
typedef struct{
//树的定义
DataNode nodes[MAX] ;
int root;//树中根结点在顺序表中的位置
int num;//树中结点个数
}CTree;
顺序表的结构为:
孩子表示法和上述的双亲表示法,我们听名字就可以知道,这种存储结构在找节点的孩子的时候十分方便,但是在找他的双亲的时候又有些麻烦,于是我们可以在这个顺序表的每个结点加上一个双亲域形成带双亲的孩子表示法。
顺序表的结构为:
3.孩子兄弟表示法
通过我们学习二叉树,我们可以知道,一个结点可以包含指向的孩子的指针,这里,我们就利用二叉树的这个特点,把一个孩子的指针改为,指向该节点的右兄弟结点。
如图所示:
存储结构定义为下:
typedef struct CSNode {
DataType data;
struct CSNode *FirstChild;//第一个孩子指针
struct CSNode *NextSibling;//右兄弟指针
} CSNode,*CSTree;
孩子兄弟结点是所讲的三种存储方式中最好操作的,他的本质就是二叉树,只不过,他的右指针的所代表的含义从右孩子变成了他的兄弟,其他的与二叉树相同,如果想找到某个结点的第n个孩子,可以先通过他的指针找到第一个孩子,然后通过第一个孩子的兄弟结点遍历n-1次,此时得到的结点就是他的第n个孩子。
接下来所讲的树,森林与二叉树之间的转化,就是基于这种结构。
话不多说,直接进入转化。
二、树,森林与二叉树之间的转化
前面对树的存储进行了概述,接下来我们来看树,森林与二叉树之间的转化
首先要告诉大家的是,此时的二叉树与树是一 一对应的关系,给定一棵树有其对应的唯一的二叉树,同理,给定一个二叉树,也有唯一对应的树(或森林)与之对应。
对应的关系图如下:
二叉树与树转化的实质就是,拿右指针为其兄弟,左指针为其孩子的二叉树解释成为一棵树,本质是与二叉树差不多的,只是他的右指针不再是他的右孩子,而是他的兄弟了,所以我们在解释的时候一定要注意他的指向含义。
根据我们对二叉树的定义,我们知道,任何一棵树对应的二叉链表的根节点的是没有兄弟的,那么如果我们遇到了,根节点有兄弟的我们应该如何理解呢?其实,这个时候,我们可以将森林中的各个树的根节点,视为兄弟,这样子,这个树我们就可以解释了,其实他是一个森林对应的二叉树。他的根节点和他的第一个孩子视为是这个森林中的第一个树,右节点则是森林中的其他的树,这样子我们对根节点有兄弟的二叉树也可以做解释。
1.树转化成二叉树
转化过程如下:
2.森林转换为二叉树
转化过程如下:
3.二叉树转化成森林
转化过程如下:
相当于2的逆过程,对于每一个右节点已经不再是右孩子,而是该结点对应的兄弟,除此之外其余正常
三、树和森林的遍历
1.树的遍历
本质与二叉树的遍历相似,对根分为先后的遍历,但是对于树来说没有中根遍历。
(1)树的遍历
①先根遍历:
首先访问根节点;
然后从左到右依次根结点的每一棵子树
②后根遍历:
首先从左到右依次根结点的每一棵子树
然后访问根节点;
(2)树的遍历算法
树的先根遍历算法①:
void RootFirst(CSTree root)
{
if(root != NULL)
{
Visit(root->data)//访问根结点
p = root->FirstChild;
while(p != NULL) //依次遍历每一棵子树
{
RootFirst(p);//递归的调用进行遍历
p = p->NextSibling;//指向下一个他的兄弟
}
}
}
树的先根遍历算法②:
void RootFirst(CSTree root){
if(root != NULL)
{
Visit(root->data)//访问根结点
RootFirst(root->FirstChild;);//先根遍历第一个子树
RootFirst(root->NextSibling);//先根遍历他的兄弟树
}
}
第二个递归的算法与二叉树的先序算法更加的像,他的后根算法只需在先根的算法基础上进行改动一下就可得到。
2.森林的遍历
(1)先序遍历:
①访问森林中的第一棵树的根结点
②先序访问第一个树的子树森林
③先序访问剩下第一棵树的兄弟构成的森林
(1)中序遍历:
①中序访问第一个树的子树森林
②访问森林中的第一棵树的根结点
③中序访问剩下第一棵树的兄弟构成的森林
总结
通过学习树和森林,我们会发现其实树和森林都是可以用二叉树来表示的,并且遍历的算法也很相似,所以二叉树是我们能够理解树和森林的关键。