树与二叉树
树和森林
树的性质(重点)
-
树总结点树=度之和+1=分支节点总和+1
- 度之和为各分支节点度数之和(如
0×n0+1×n1+2×n2+...
, n i n_i ni为度为i的节点数)。
- 度之和为各分支节点度数之和(如
-
第i层最多有m^(i-1)个结点(m为树的度,即节点的最大子节点数)。
-
高度为h的m次树的总结点最多为(m^h-1)/(m-1)
- 推导:等比数列求和,首项1,公比m,共h项。
-
具有n个结点的m次树的最小高度为⌈log_m(n(m-1)+1)⌉*
- 保证树高度最小时,各层节点数尽可能多,需满足
(m^h-1)/(m-1) ≥ n
。
- 保证树高度最小时,各层节点数尽可能多,需满足
树的遍历
-
先根遍历
- 顺序:先访问根节点,再递归遍历各子树。
- 等价性:与森林的先根遍历、二叉树的先序遍历结果一致(需转换为对应结构)。
-
后根遍历
- 顺序:先递归遍历各子树,再访问根节点。
- 等价性:与森林的后根遍历、二叉树的中序遍历结果一致。
-
层次遍历
- 顺序:按层从左到右访问节点,等价于图的广度优先遍历(BFS)。
树的存储结构
1. 双亲存储
typedef struct {
elemtype data; // 结点值
int parent; // 双亲位置(数组下标)
} PTree[Maxsize]; // 双亲存储结构类型
- 优点:快速查找节点的双亲。
- 缺点:查找子节点需遍历整个数组。
2. 孩子链存储
typedef struct node {
elemtype data;
struct node *sons[Maxsons]; // 指向孩子结点的指针数组
} TSonNode;
- 空域计算:指针总数-分支数 = n×m - (n-1)(m为Maxsons,n为节点数)。
- 求树高度的递归算法:
int treeheight(TSonNode *t) { TSonNode *p; int i, h, maxh = 0; if (t == NULL) return 0; for (i = 0; i < Maxsons; i++) { p = t->sons[i]; if (p != NULL) { h = treeheight(p); if (maxh < h) maxh = h; } } return maxh + 1; // 根节点高度+1 }
3. 兄弟链存储(左孩子右兄弟)
typedef struct tnode {
elemtype data;
struct tnode *hp; // 指向兄弟
struct tnode *vp; // 指向第一个孩子
} TsbNode;
- 核心思想:用“左孩子、右兄弟”表示树结构,便于树与二叉树转换。
- 空域计算:指针总数-分支数 = 2n - (n-1) = n+1(每个节点2个指针)。
- 求树高度的递归算法:
int treeheight2(TsbNode *t) { TsbNode *p; int h, maxh = 0; if (t == NULL) return 0; p = t->vp; // 指向第一个孩子 while (p != NULL) { h = treeheight2(p); if (maxh < h) maxh = h; p = p->hp; // 遍历兄弟 } return maxh + 1; }
二叉树
(完全)二叉树的性质
- n0 = n2 + 1(叶子节点数=度为2的节点数+1)。
- n = n0 + n1 + n2 = 2n0 + n1 - 1(n为总节点数,n1为度为1的节点数)。
- **第i层最多有2(i-1)个结点**,深度为k的二叉树最多有2k - 1个结点。
- 完全二叉树深度为⌊log2(n)⌋ + 1(n为节点数)。
- 层序编号规则(编号从1开始):
- 若i≤n/2,i为分支节点;否则为叶子节点。
- 左孩子编号2i,右孩子编号2i+1,父节点编号⌊i/2⌋。
二叉树遍历
1. 先序遍历(根-左-右)
void preOrder(btNode *b) {
if (b != NULL) {
printf("%c", b->data); // 访问根
preOrder(b->lchild); // 递归左子树
preOrder(b->rchild); // 递归右子树
}
}
- 结果类似前缀表达式,第一个节点为根。
2. 中序遍历(左-根-右)
void inOrder(btNode *b) {
if (b != NULL) {
inOrder(b->lchild); // 递归左子树
printf("%c", b->data); // 访问根
inOrder(b->rchild); // 递归右子树
}
}
- 结果类似中缀表达式,根节点在左右子树之间。
3. 后序遍历(左-右-根)
void postOrder(btNode *b) {
if (b != NULL) {
postOrder(b->lchild); // 递归左子树
postOrder(b->rchild); // 递归右子树
printf("%c", b->data); // 访问根
}
}
- 结果类似后缀表达式,最后一个节点为根。
4. 层次遍历
- 非递归实现,借助队列按层访问,先入队根节点,再依次入队左右子节点。
二叉树、树、森林的转换
1. 二叉树转树
- 规则:
- 二叉树的左孩子为树中该节点的最左孩子。
- 二叉树的右孩子为树中该节点的兄弟。
- 要求:根节点无右孩子(否则转成森林)。
- 方法:将右孩子连线左转45°,断开兄弟间连线,连接至双亲。
2. 二叉树转森林
- 规则:同二叉树转树,但根节点需有右子树。
- 方法:断开根节点与右子树的连线,右子树转为森林的其他树。
3. 树转二叉树
- 规则:第一棵子树为左孩子,下一个兄弟为右孩子。
- 方法:兄弟间连虚线,删除非最左子树连线,虚线右转45°。
4. 森林转二叉树
- 方法:将森林中各树转二叉树后,以第一棵树的根为根,其他树的根作为其右孩子。
二叉树的存储结构
1. 顺序存储
typedef char elemtype;
typedef elemtype SqBinTree[Maxsize]; // 课本定义
- 适用于完全二叉树,按层序存储,空节点用特殊符号表示(如
#
)。
2. 二叉链表
typedef char elemtype;
typedef struct node {
elemtype data;
struct node *lchild, *rchild;
} btNode;
- 空域计算:总指针数2n,分支数n-1,空域数2n - (n-1) = n+1。
3. 三叉链表
typedef char elemtype;
typedef struct node {
elemtype data;
struct node *lchild, *rchild, *parent;
} btNode;
- 增加父指针,便于查找祖先,空域数3n - 2(n-1) = n+2。
二叉树的构造
- 先序+中序构造:
- 先序第一个节点为根,中序中根左侧为左子树,右侧为右子树,递归构造。
- 后序+中序构造:
- 后序最后一个节点为根,中序中划分左右子树,递归构造。
- 层次+中序构造:
- 层次第一个节点为根,中序中划分左右子树,结合层次序列确定子树顺序。
特殊二叉树与应用
线索二叉树
- 目的:利用空指针记录前驱和后继,提高遍历效率。
- 规则:
- 左空指针指向中序前驱,右空指针指向中序后继。
- 增加标志位区分指针指向子节点或线索。
二叉排序树(BST)
- 性质:左子树所有节点值<根节点<右子树所有节点值。
- 应用:高效查找、插入、删除,平均时间复杂度O(logn)。
平衡二叉树(AVL树)
- 性质:任意节点的左右子树高度差≤1。
- 目的:保证查找效率,避免退化为链表。
堆
- 性质:
- 完全二叉树,分为大根堆(父节点≥子节点)和小根堆(父节点≤子节点)。
- 用数组存储,i节点的左孩子2i+1,右孩子2i+2(从0开始编号)。
- 应用:优先级队列、堆排序。
哈夫曼树(最优二叉树)
1. 构造步骤(重点)
- 选两个权值最小的节点合并为新节点,新节点权值为两节点权值之和。
- 重复步骤1,直到只剩一个节点(共合并n-1次,n为叶子节点数)。
2. 带权路径长度(WPL)计算
- 公式:WPL = Σ(叶子节点权值×路径长度)。
- 示例:叶子节点权值为{1,2,3,4},构造哈夫曼树的WPL=1×3+2×3+3×2+4×2=21。
3. 哈夫曼编码(前缀编码)
- 规则:左子树编码0,右子树编码1,叶子节点的编码为从根到叶子的路径序列。
- 性质:无任何编码是其他编码的前缀,用于数据压缩。
4. 关键结论
- 哈夫曼树中n0 = n2 + 1,且无度为1的节点(n1=0)。
- 总节点数=2n0 - 1=2n2 + 1。
并查集
- 功能:支持集合的合并(Union)和查找(Find)操作。
- 实现:用双亲存储结构,每个节点记录父节点,路径压缩优化查找效率。
图示参考
- 树的性质示意图:
- 树与二叉树转换示例:
- 二叉树存储结构示意图:
- 哈夫曼树构造示例: