数据结构(16)树与森林
前言
树是n(n>=0)个结点的有限集。在任意一棵非空树中:1.有且只有一个特定的称为根的结点 2.当n>1时,其余结点可分为m(m>=0)个互不相交的有限集,并且称为根的子树。可以发现,在树的定义中,又用到了树的概念,树的结构定义是一个递归的定义。
森林则是m(m>=0)棵互不相交的树的集合。对树中的每一个结点而言,其子树的集合即为森林。因此,也可以用森林和树相互递归的定义来描述树。
树的应用很广泛,但是由于树的形状可以任意,显然对树的处理会复杂许多。
树的存储结构
双亲表示法
在双亲表示法中,结点拥有一个数据域和一个指针域,其中指针域指向结点的双亲结点。
(注:”双亲“结点即父结点,并不意味着两个有结点,搞不懂为啥要这样翻译)
这里我们以顺序存储为例子,假设用一个数组来保存整棵树,那么数组下标就是结点的位置。
如果用这种方式,那么结点的结构如下:
typedef struct PTNode{
//数据域
ElemType data;
//指针域
int parent;
}PTNode;
使用这种表示法的缺点也很明显:假如想得到结点的孩子,需要遍历整个结构才能得到。
孩子表示法
顾名思义,孩子表示法即指针域指向的是结点的孩子结点。但是与二叉树不同,一棵树可以拥有多个结点,那么指针域该如何设计呢?
第一种方法是采用固定指针域数量的结点,也就是说结点的格式是一致的。
typedef struct CTNode{
//数据域
ElemType data;
//指针域
int child1;
int child2;
//...
int childd;
}CTNode;
这种方式的弊端很明显:假设某个结点的孩子结点数量很多,而其他结点的孩子结点数量很少时,由于采取了统一格式,所有结点的指针域数量必须同最大的数量一致,这样就造成了浪费。
因此可以采取第二种方式:增加一个数据域用于记录结点的度,根据度来确定指针域的个数。
typedef struct CTNode{
//数据域
ElemType data;
//结点的度
int degree;
//指针域
int child1;
//...
}CTNode;
显然,这种方法虽然能节约空间,但是每个结点的结构不一样,操作上很不方便。
有一种改进方法是,将每个结点的孩子结点排列起来,看成一个线性表,以单链表作为存储结构。
这样,我们需要设计一个孩子结点,作为该链表存储的对象。
//孩子结点
typedef struct ChildNode{
//孩子结点的位置
int child;
//指向下一个孩子结点
struct ChildNode* next;
}ChildNode;
树的结点:
//树的结点
typedef struct CTNode{
//数据域
ElemType data;
//指针域-孩子结点
ChildNode* firstChild;
}CTNode;
采用这种方式很容易找到孩子结点,但是不容易找到父结点。可以与双亲表示法结合起来,使操作更简便。
孩子兄弟表示法
孩子兄弟表示法又称为二叉树表示法,即用二叉链表作为树的存储结构。这样,树的结点一定有两个指针域,规定将左结点的指针域指向该结点的第一个孩子结点,右结点的指针域指向结点的下一个兄弟结点,得到孩子兄弟表示法的结点结构如下:
typedef struct CSNode{
//数据域
ElemType data;
//指针域-左结点指向第一个孩子结点
struct CSNode* firstChild;
//指针域-右结点指向下一个兄弟结点
struct CSNode* nextSibling;
}CSNode;
用孩子兄弟表示法来表示树,可以看做是将一棵树转换为二叉树。
树、森林与二叉树的转换
树转换为二叉树
前面提到,孩子兄弟表示法即使用二叉链表来存储树。
作为二叉链表,除了数据域外,它必定只有两个指针域,指向其左结点和右结点。
在二叉树中,左结点指向的是左子树,右结点指向的是右子树。
在线索二叉树中,左结点指向左子树或者前驱,右结点指向右子树或后继。
在树中,没有左右子树的说法,那么用左结点指向它的第一个孩子结点,右结点指向它的下一个兄弟结点。
示例:
- 首先存储R,其第一个孩子结点为A,则R的左结点为A;R无兄弟结点,右结点为空
- A的第一个孩子结点为D,则A的左结点为D;A的下一个兄弟节点为B,则其右结点为B
- D无孩子结点,则其左结点为空;D的下一个兄弟结点为E,则其右结点为E
- E无孩子结点,则其左结点为空;E无下一个兄弟,则其右结点也为空
- B无孩子结点,则其左结点为空;B的下一个兄弟结点为C,则其右结点为C
- C的第一个孩子结点为F,则其左结点为F;C无下一个兄弟,则其右结点为空
- F的第一个孩子结点为G,则其左结点为G;F无兄弟,则其右结点为空
- G无孩子结点,则其左结点为空;G的下一个兄弟结点为H,则其右结点为H
- H无孩子结点,则其左结点为空;H的下一个兄弟结点为K,则其右结点为K
- K无孩子结点,则其左结点为空;K无下一个兄弟,则其右孩子为空
由此可见,在物理结构上,二叉树与孩子兄弟表示法的树是一致的,只是解释方法不同,含义就不同了。
由于根结点没有兄弟,因此根结点的右结点必为空。
森林转换为二叉树
森林是由若干棵树组成的,因此每一棵树都能各自转换为二叉树。同时可以认为,森林中的每一棵树都是兄弟,即根结点的兄弟结点是下一棵树的根结点。基于这两点,森林转换为二叉树是很容易的。