18、树和森林的存储结构、转换、遍历
一、树的存储结构
1、双亲存储表示法
一般采用顺序存储结构实现。用一组地址连续的存储单元来存放树的结点,每个结点有两个域:
data域-----存放结点的信息;
parent域-----存放该结点双亲结点的位置。
特点:求结点的双亲很容易,但求结点的孩子需要遍历整个向量。
存储结构描述为:
#define MaxTreeSize 100 //定义数组空间的大小
typedef char DataType; //定义数据类型
typedef struct
{ DataType data; //结点数据
int parent; //双亲指针,指示结点的双亲在数组中的位置
} PTreeNode;
typedef struct
{
PTreeNode nodes[MaxTreeSize];
int n; //结点总数
} PTree;
PTree T; //T是双亲链表
2、孩子链表表示法
这是树的链式存储结构。每个结点的孩子用单链表存储,称为孩子链表。
n个结点可以有n个孩子链表(叶结点的孩子链表为空表)。
n个孩子链表的头指针用一个向量表示。
它的特点是:与双亲相反,求孩子易,求双亲难。
如图所示:
存储结构描述为:
typedef structCTNode
{ int child; //孩子链表结点
struct CTNode *next;
}*ChildPtr;
typedefstruct //孩子链表头结点
{ ElemType data; //结点的数据元素
ChildPtrfirstchild; //孩子链表头指针
}CTBox;
typedef struct
{ CTBoxnodes[MaxTreeSize];
int n, r, //数的结点数和根结点的位置
} CTree;
3、孩子兄弟链表表示法
孩子兄弟链表表示法也是树的一种链式存储结构。用二叉链表作为树的存储结构,每个结点的左链域指向该结点的第一个孩子,右链域指向下一个兄弟结点。
由于结点中的两个指针指示的分别为“孩子”和“兄弟”,故称为“孩子-兄弟链表”。这种结构也称为二叉链表。
特点:双亲只管长子,长子连接兄弟。
树的孩子兄弟链表的存储结构描述为:
typedef struct CSNode
{
ElemTypedata;
structCSNode *firstchild, *nextsibling;
} CSNode,*CSTree;
孩子兄弟存储结构的最大优点是可以方便地实现树和二叉树的相互转换和树的各种操作。但是,孩子兄弟存储结构的缺点也是查找当前结点的双亲结点比较麻烦,需要从树根结点开始逐个结点比较查找。
二、森林和二叉树的转换
树与二叉树均可用二叉链表作为存储结构,因此给定一棵树,用二叉链表存储,可唯一对应一棵二叉树,反之亦然。
如图所示:
将一棵树转化为等价的二叉树方法如下:
(1) 在树中各兄弟(堂兄弟除外)之间加一根连线。
(2) 对于任一结点,只保留它与最左孩子的连线外,删去它与其余孩子之间的连线。
(3) 以树根为轴心,将整棵树按顺时钟方向旋转约45°。
特点:根无右子树
树和森林都可转换成二叉树,但树转换成二叉树后根结点无右分支,而森林转换后的二叉树,其根结点有右分支。
将森林转化为二叉树方法如下:
(1) 将森林中的每一棵树转换成等价的二叉树。
(2) 保留第一棵二叉树,自第二棵二叉树始,依次将后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,当所有的二叉树依此相连后,所得到的二叉树就是由森林转化成的二叉树。
(3) 以树根为轴心,将整棵树按顺时钟方向旋转约45°。
三、森林的遍历
森林的深度优先遍历通常也有两种方式:前序遍历和后序遍历。
(1) 前序遍历森林
若森林非空,则:
访问森林中第一棵树的根结点;
前序遍历第一棵树中根结点的各子树所构成的森林
前序遍历去掉第一棵树外其它树构成的森林。
(2) 后序遍历森林
若森林非空,则:
后序遍历森林中第一棵树中根结点的各子树所构成的森林;
访问第一棵树的根结点;
后序遍历去掉第一棵树外其它树构成的森林。
当用二叉链表作为树和森林的存储结构时,树和森林的前序遍历和后序遍历可用二叉树的前序遍历和中序遍历算法来实现。