树
需掌握树的概念和存储结构,以及与森林和二叉树之间的转换,在选择题中考察。
树的基本概念
结点属性
- 双亲(parent):如果一个结点包含子结点,则该结点被称为其子结点的双亲。
- 兄弟(sibling):具有相同双亲结点的结点互称为兄弟结点。
- 孩子(child):一个结点直接连接到另一个结点,并且位于较低的层级,则该结点被称为子结点或孩子。
度
- 结点的度:结点的孩子数量
- 树的度:等于树中所有结点度的最大值
- 分支结点(非终端结点):度大于 0 的结点
- 叶子结点(终端结点):度等于 0 的结点
深度
- 树的深度(Depth):从根结点到最远叶子结点的最长路径上的边的数量。
- 结点的深度:是指从根结点到该结点的路径长度。
高度
- 树的高度(Height):从根结点到最远叶子结点的最长路径上的边的数量。
- 结点的高度:从该结点到其最远叶子结点的最长路径上的边的数量。
注意
深度定义是从上往下的,高度定义是从下往上的。
一般不用在意这个,因为树的高度和深度是相同的,但是结点的高度和深度可能会不同。
路径
- 路径:在一棵树中,从一个结点到另一个结点所经过的所有结点,被称为这两个结点之间的路径。
- 结点的权:每个结点被赋予的一个数值,通常表示该结点的重要性或频率。
- 结点的带权路径长度:从根结点到该结点的路径长度与该结点权值的乘积。
- 树的带权路径长度:所有叶子结点的带权路径长度之和。
树的存储结构
树的存储结构是指在计算机中如何表示和存储树这种数据结构, 这里主要了解双亲表示法、孩子表示法 和 孩子兄弟表示法即可。
双亲表示法
双亲表示法主要是使用一个数组,其中每个结点都有一个指示其双亲结点在数组中位置的索引。
#define MAXSIZE 100
typedef struct {
int data; // 结点数据
int parent; // 双亲的位置
} PTNode;
typedef struct {
PTNode nodes[MAXSIZE]; // 结点数组
int n; // 结点数
} PTree;
孩子表示法
孩子表示法将每个结点的孩子结点排列起来,以单链表作为存储结构。然后再用一个数组与之相配合。
#define MAXSIZE 100
// 孩子结点
typedef struct ChildNode {
int child; // 孩子结点在数组中的位置
struct ChildNode* next; // 下一个孩子
} *ChildPtr;
// 表头结构
typedef struct {
int data; // 结点数据
ChildPtr firstchild; // 第一个孩子的指针
} CTBox;
typedef struct {
CTBox nodes[MAXSIZE]; // 结点数组
int n; // 结点数
} CTree;
孩子兄弟表示法
孩子兄弟表示法是将树转化为二叉树的形式来存储。每个结点有两个指针,一个指向它的第一个孩子,另一个指向它的右兄弟。
typedef struct CSNode {
int data; // 结点数据
struct CSNode* firstchild; // 第一个孩子
struct CSNode* rightsib; // 右兄弟
} CSNode, *CSTree;
森林的基本概念
森林(Forest)是一组互不相交的树的集合。换句话说,森林由若干棵树组成,每棵树都是一个独立的层次结构,且这些树之间没有连接关系。
- 森林与树的区别:
- 一棵树只有一个根结点,而森林可以有多个根结点(每棵树一个)。
- 森林可以看作是多个树的并集,树是森林的一个特例(森林中只有一棵树)。
- 森林的深度:森林中所有树的最大高度(从根到最远叶结点的路径长度)。
树、森林和二叉树的转换
树转化为二叉树
- 若树的根结点有孩子,那么第一个孩子是二叉树的左孩子,其他的孩子结点依次作为前一个孩子结点的右孩子。
- 对每个孩子执行上述步骤。
森林转二叉树
- 把森林中的每一棵树转换为二叉树。
- 第一棵二叉树不动,从第二棵二叉树开始,依次将后一棵二叉树的根作为前一棵二叉树的右孩子。其结果是一个二叉树。
树和森林的遍历
树的遍历
对于一个给定的树,通常有以下两种遍历方式:
- 先根遍历(类似于二叉树的前序遍历):
- 访问树的根结点。
- 递归地先根遍历根的每一棵子树。
- 后根遍历(类似于二叉树的后序遍历):
- 递归地后根遍历根的每一棵子树。
- 访问树的根结点。
注意树是没有中根遍历的,除非这棵树是二叉树。
#define MAXCHILD 20
typedef struct TreeNode {
int value;
int numChildren; // 子结点的数量
struct TreeNode *children[MAXCHILD]; // 子结点指针数组
} TreeNode;
void preOrderTraversal(TreeNode* root) {
if (root == NULL) {
return;
}
// 先访问根结点
// 然后遍历子结点
for (int i = 0; i < root->numChildren; ++i) {
preOrderTraversal(root->children[i]);
}
}
void postOrderTraversal(TreeNode* root) {
if (root == NULL) {
return;
}
// 先遍历子结点
for (int i = 0; i < root->numChildren; ++i) {
postOrderTraversal(root->children[i]);
}
// 然后访问根结点
}
对于上图所示的树:其先根遍历为 A, B, E, F, C, D, G
,后根遍历为E, F, B, C, G, D, A
。
观察可以得到如下结论:
- 树的先根遍历和其对应的二叉树的先序遍历相同
- 树的后根遍历和其对应的二叉树的中序遍历相同
森林的遍历
对于一个给定的森林,遍历方式如下:
- 先根遍历(与树的先根遍历相似):依次先根遍历森林中的每一棵树。
- 后根遍历(与树的后根遍历相似):依次后根遍历森林中的每一棵树。
- 中根遍历(普通的树构成的森林是不存在中序遍历的,这里的中序遍历指代的是二叉树森林):依次中序遍历森林中的每一棵二叉树。
对于上图所示的森林:其先根遍历为 A, B, C, D, E, F, G, H, I
,后根遍历为 B, C, D, A, F, E, H, I, G
,中根遍历为 B, C, D, A, F, E, H, I, G
观察可以得到如下结论:
- 森林的先根遍历和其对应的二叉树的先序遍历相同
- 森林的中根遍历和其对应的二叉树的中序遍历相同