树的存储结构及二叉树与树的转换
双亲表示法(顺序存储)
每个结点中保存指向双亲的“指针”。(类似于静态链表)
根结点存储在数组0号位置,-1表示没有双亲。
#define MAX_TREE_SIZE 100 //树中最多结点数
//树的结点定义
typedef struct{
ElemType data;
int parent;
}PTNode;
//树的类型定义
typedef struct{
PTNode nodes[MAX_TREE_SIZE];
int n;
}PTree;
增加
双亲表示法增加一个结点很容易,只需在数组后加入对应的data,parent。增加的结点不必按逻辑上的次序存储。
删除
删除一个结点分两种情况:
如果删除的结点是叶子结点
把结点的parent置为-1。
把尾部结点数据移到要删除的结点位置。这样可以保证数组中的结点数据都是有效的。
注意:删除一个结点后,还需要更改结点数n。
如果删除的结点不是叶子结点。则还要把该结点的子孙结点都删除。
因此就涉及到查询操作,即从一个结点找到它的孩子结点。使用双亲表示法的存储结构利用了每个结点(除根结点外)都有唯一双亲的性质,可以很快得到每个结点的双亲结点,但求结点的孩子时需要从数组头部开始遍历整个数组。
(从而可以看出,如果使用直接将parent置为-1的方法,则会导致在遍历数组时多次无效的开销。因此推荐使用第二种删除方法)
孩子表示法(顺序存储+链式存储)
这种存储方式寻找子女的操作非常直接,而寻找双亲的操作需要遍历n个结点中孩子链表指针域所指向的n个孩子链表。
#define MAX_TREE_SIZE 100
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;
孩子兄弟表示法(链式存储)
孩子兄弟表示法又称二叉树表示法,即以二叉链表作为树的存储结构。
每个结点包含三部分内容:结点值、指向结点第一个孩子结点的指针,及指向结点下一个兄弟结点的指针(沿次指针域可以找到结点的所有兄弟结点)。
将树转换为二叉树的链式存储。
树与二叉树的转换
其实就是利用树的孩子兄弟表示法的规则,将树转换为二叉链表。
树中结点的第一个孩子,相当于二叉树中结点的左孩子。
树中结点的兄弟结点,相当于二叉树中结点的右孩子。
二叉树转树
二叉树的叶子结点和树的叶子结点数并不一定相同。
森林和二叉树的转换
森林转换成二叉树
本质也是可以用二叉链表存储森林。先将每个树转换为二叉树,然后将每个树的根结点看成兄弟结点,依次连接各个二叉树。就可以得到森林对应的二叉树。
二叉树转换成森林
总结:树、森林和其对应二叉树的某些性质之间的联系
遍历树/森林对应的二叉树时,往左是下级,往右是平级。简称:左孩子,右兄弟。
叶子结点和非叶子结点的关系:
②森林/树的叶子结点的个数 = 森林/树的总结点个数 - 森林/树的非叶子结点个数
③森林/树的总结点个数 = 对应二叉树的总结点个数
由上可以推出一系列的推论:
①森林中非叶子结点个数 + 1 = 对应二叉树中右指针域为空的结点个数
注:树或森林中非叶子结点说明一定有孩子,那么它的最后一个孩子在对应二叉树中右指针域一定为空,再加上最后一个
②森林中叶子结点个数 = 对应二叉树中左指针域为空的结点个数
注:森林或树中叶子结点没有孩子,对应于二叉树中左指针域为空
度的关系
树的度数 = 其对应二叉树的度数
森林的度数 = 总结点数 - 森林中树的个数 = 对应二叉树的度数 + 1 - 森林中树的个数
高度/深度的关系:
森林中树的个数等于二叉树中从根结点往右走的结点数,所以不一定等于二叉树的高度。如果二叉树是满二叉树,则高度等于对应森林中树的个数。
树和森林的高度都不一定 = 对应二叉树的高度。