5.4.1 树的存储结构
1.树的逻辑结构 2.双亲表示法 3.孩子表示法
4.孩子兄弟表示法 5.树、森林与二叉树的转换
1.树的逻辑结构
递归定义;
1)一个根结点;
2)互不相交的子树。
2.双亲表示法(顺序存储)
利用数组存储指向双亲的“指针”
根结点固定存储在0,用-1表示没有双亲
//双亲表示
#define MAX_TREE_SIZE 100 //最多结点数目
typedef struct{
ElemType data; //数据元素
int parent; //双亲位置域
}PTNode;
typedef struct{ //树的类型定义
PTNode nodes[MAX_TREE_SIZE]; //双亲表示,结构体数组存储
int n; //结点数
}PTree;
增删查:
1)增:新增数据元素,无需按逻辑上的顺序存储
2)删:
2.1)删除叶子结点:
【方案一】:双亲“指针”设为-1
【方案二】:尾部数据上移至删除数据
注意:删除结点后,结点数目n记得减1
2.2)删除非叶子结点:
需要连同其子孙一起删除,需要找到孩子:只能从头遍历
【回顾】:二叉树的顺序存储
一定要把二叉树的结点编号和完全二叉树对应起来
i的左孩子:2i
i的右孩子:2i+1
i的父结点:[i/2]
结点编号不仅反映了存储位置,也隐含了结点之间的逻辑关系
3.孩子表示法 (顺序+链式存储)
顺序存储各个结点,每个结点中保存孩子链表头指针,并指向第一个孩子
//孩子表示
//链表中的各个结点,实际存储各个结点的下标
struct CTNode{
int child;//孩子结点在数组中的位置
struct CTNode *next;//下一个孩子
};
//各个结点实际数据存储结构
typedef struct{
ElemType data;
struct CTNode *firstChild;//第一个孩子
}CTBox;
typedef struct{
CTBox nodes[MAX_TREE_SIZE];
int n,r;//结点数和根的位置
}CTree;
4.孩子兄弟表示法(链式存储)
//树的存储:孩子兄弟表示法
typedef struct CSNode{
ElemType data;//数据域
struct CS *firstchild,*nextsibling;//第一个孩子指针,和右兄弟指针
}CSNode,*CSTree;
类似二叉链表
根结点没有右兄弟指针,只有第一个孩子的指针
5.树、森林与二叉树的转换
森林:m棵互不相交的树的集合
森林的存储:各个根结点可以看作兄弟关系,因此可以用右指针相连
本质:用二叉链表存储森林
5.4.2 树、森林的遍历
1.树的遍历:1)先根 2)后根 3)层序
2.森林的遍历:1)先序 2)中序
1.树的遍历
具有递归结构,可以利用递归遍历
1)树的先根遍历:先访问根结点
//树的先根遍历
//伪代码
void PreOrder(TreeNode *R){
if(R!=NULL){
visit(R);//访问根结点
while(R还有下一个子树)
PreOrder(T);//先遍历下一棵树
}
}
树和二叉树的转换:用二叉链表存储
树的先根遍历序列与这棵树相应二叉树的先序序列相同
2)树的后根遍历:最后访问根结点
//树的后根遍历
//伪代码
void PostOrder(TreeNode *R){
if(R!=NULL){
while(R还有下一个子树)
PreOrder(T);//后根遍历下一棵树
visit(R);//最后访问根结点
}
}
树的后根遍历序列与这棵树相应二叉树的中序序列相同
3)树的层次遍历
类似二叉树,也要设置一个辅助队列:
(1)若树非空,则根结点入队;
(2)若队列非空,队头元素出队并访问,同时将该元素的孩子依次入队;
(3)重复(2)直至队列为空。
也可称为:对树的广度优先遍历
相对地,后根和先根遍历可以认为是深度优先遍历。
2.森林的遍历
1)森林的先序遍历
(1)访问森林中第一棵树的根结点
(2)先序遍历第一棵树中根结点的子树森林
(3)先序遍历除去第一棵树之后剩余的树构成的森林
效果等同于依次对各个树进行先根遍历。
也可以将森林用孩子兄弟方法,转换成二叉树,效果等同于依次对二叉树进行先序遍历。
2)森林的中序遍历
效果等同于依次对各个树进行后根遍历。
转换称为二叉树,效果等同于依次对二叉树进行中序遍历。
森林遍历的代码:转换成二叉树操作。
【总结】
等价关系:
1)树:先根; 森林:先序; 二叉树:先序。
2)树:后根; 森林:中序; 二叉树:中序。