基本概念
- 形式化定义(二元组)
树:T={D,R}.数据集合与关系集合
D是包含n个结点的有限集合(n>=0)
n=0 空树,n>0有且仅有一个没有前驱的结点,称为root;其他结点只有一个前驱,但是可以有多个后继 - 递归定义
树可以是空树(无节点),或由以下部分组成:
- 一个根节点;
- 若干互不相交的子树(每棵子树本身也是一棵树)。
n=0 空树 n>0 根节点及m个不相交的子树构成
核心概念
-
度:后继节点的个数
度练习题
1. Eg.
一颗度为4的树T中,若有20个度为4的结点,10个度为3的结点,1个度为2的结点,10个度为1的节点,则树的叶子结点个数为()
A.41 B.82 C.133 D.122度之和122 需要加1=123 减去度不为0的部分123-41=82 选B
-
分支节点:一个结点的度非零称为分支结点
-
叶子节点:一个结点的度0 称为叶子结点
-
边:连接相邻两个节点
-
高度、深度
-
路径:任意两个节点之间的边,路径唯一
-
n个结点,所有结点度之和是多少?等于边的个数n-1
-
d3:4,d2:5,d1:7,有多少个结点,多少个叶子结点
- 3 * 4+2 * 5+1 * 7=29 边 +1 = 30结点,4+5+7=16分支节点
- 30-16=14个叶子结点
-
深度:从根结点(从0开始或者从1开始,不同资料不同开始,子节点层次递增
-
高度:从结点到叶子结点路径中边的个数
-
双亲结点:前驱结点 1
-
子结点:后继结点 0~n
-
兄弟结点:有公共双亲的结点
-
非空二叉树上叶结点数等于双分支结点数加1,即:n0=n2+1
- 已知一颗完全二叉树的第6层(设根为第1层)有8个叶子结点,则该完全二叉树的结点个数最多是()
A.39 B.52 C.111 D.119
- 已知一颗完全二叉树的第6层(设根为第1层)有8个叶子结点,则该完全二叉树的结点个数最多是()
节点:n
边: n-1
度之和:n-1
操作
- 查找
- 插入
- 删除
- 遍历
- 先根(序)遍历
- 后根
- 层序
- 中序(二叉树)
树的存储
- 双亲存储 ‘ * ’ 顺序存储基于数组
- 孩子链存储 指针个数过多
- 孩子兄弟存储 ‘*’链式存储,基于指针
树的操作
- 先序
- 后序
- 中序 ’ * ’
- 层序
遍历方式 | 访问顺序 | 递归实现代码框架 |
---|---|---|
前序遍历 | 根→左→右 | visit(root); preOrder(left); preOrder(right); |
中序遍历 | 左→根→右 | preOrder(left); visit(root); preOrder(right); |
后序遍历 | 左→右→根 | preOrder(left); preOrder(right); visit(root); |
=
先序:ABDHIEJKCFLMG
后序:HIDJKEBLMFGCA
中序:HDIBJEKALFMCG
层序:ABCDEFGHIJKLM
EG2.由后序中序推图
后序:GDBEFCA
中序:DGBAECF
二叉树性质
-
二叉树与二次树有什么区别?
- 有限元素的集合,树,非空,由左子树和右子树构成
-
满二叉树:分支结点的度都为2,叶子结点在最底下一层:2^n
-
完全二叉树:分支结点的度都为2,叶子结点后两层:除了最底下一层,上面是满二叉树
-
搜索二叉树
-
哈夫曼树
-
堆(优先级队列)
线索二叉树
具有n个结点的二叉树
二叉搜索树 BST
- 左子树中的节点小于根节点
- 右子树中的节点大于根节点
子树以下也是按照左小右大排序
- 例题:给出数字13,9,42,3,5,1,19,72,32,7,4画二叉树
二叉搜索树的中序遍历即为从小到大排列
特殊:平衡二叉搜索树
- AVL
- 红黑树
n个结点
最坏 n层
最好log(N+1)
堆
基于完全二叉树构成的堆
- 最小堆(堆顶最小)
- 最大堆(堆顶最大)
堆排序,优先级队列
存储结构:数组
二叉树
13,9,42,3,5,1,19,72,32
叶子结点n双亲就是(n-1)/2
如果将已经排好的堆堆顶拿走,拿最后一个叶子结点来补充,然后重新调整,避免完全二叉树的结构错乱
双亲 n 找孩子 左孩子2n+1 右孩子2n+2
示例
哈夫曼树
具有最小带权路径长度的二叉树称为哈夫曼树(也称最优树)
定长编码 ANSI 1byte 8bit,GB2312/GBK 2byte 16bit
变长编码 UTF-8 1byte, 3byte,
this is a line
2^8 = 256种
2^16 = 65536
t 1
h 1
i 3
s 2
a 1
l 1
n 1
e 1
哈夫曼树构造 选两个最小的树相加,结果再次放入树堆里,再次选两个小的,直到最后结束
叶子结点是分支结点+1
叶子结点是可以编码的
- 规定哈夫曼树中的左分支为0,右分支为1,则从根结点到每个叶结点所经过的分支对应的0和1组成的序列便为该结点对应字符的编码。这样的编码称为哈夫曼编码。
- 权值越大的字符编码越短,反之越长
** 哈夫曼树定义**
哈夫曼树(最优二叉树)是带权路径长度(WPL)最短的树,其中:
- WPL:所有叶子节点权值 × 路径长度之和。
- 构造方法:贪心算法,每次合并权值最小的两棵树。
构造步骤
- 统计频率:统计字符出现频率作为权值。
- 初始化森林:每个权值作为独立二叉树。
- 合并子树:重复合并最小两棵树,生成新父节点(权值为子节点和),直至只剩一棵树。
示例:权值集合 {5, 9, 12, 13, 16} 的构造过程:
合并5+9→14 → 合并12+13→25 → 合并14+16→30 → 合并25+30→55
最终树结构:根权值55,WPL=5×3 + 9×3 + 12×2 + 13×2 + 16×2 = 142
哈夫曼编码
- 前缀编码:无歧义解码,左分支标记0,右分支标记1。
- 编码生成:从根到叶子的路径即为二进制编码。
C语言实现哈夫曼树与编码**
** 数据结构定义**
typedef struct HuffmanNode {
int weight; // 权值
char data; // 字符(可选)
struct HuffmanNode *left; // 左孩子
struct HuffmanNode *right; // 右孩子
} HuffmanNode;
核心代码实现
// 创建哈夫曼树(基于最小堆)
HuffmanNode* buildHuffmanTree(char data[], int freq[], int size) {
// 初始化最小堆(代码参考网页2)
MinHeap* heap = createMinHeap(size);
for (int i = 0; i < size; i++) {
insertMinHeap(heap, createNode(data[i], freq[i]));
}
// 合并最小权值节点
while (heap->size > 1) {
HuffmanNode* left = extractMin(heap);
HuffmanNode* right = extractMin(heap);
HuffmanNode* parent = createNode('$', left->weight + right->weight);
parent->left = left;
parent->right = right;
insertMinHeap(heap, parent);
}
return extractMin(heap);
}
// 生成哈夫曼编码表
void generateCodes(HuffmanNode* root, int codes[], int top) {
if (root->left) {
codes[top] = 0;
generateCodes(root->left, codes, top + 1);
}
if (root->right) {
codes[top] = 1;
generateCodes(root->right, codes, top + 1);
}
if (!root->left && !root->right) {
printf("%c: ", root->data);
for (int i = 0; i < top; i++) printf("%d", codes[i]);
printf("\n");
}
}
应用示例
对字符串 “AFTERDATAEARARE” 进行编码:
- 统计频率:A(5), E(4), R(3), T(2), F(1), D(1)
- 构造哈夫曼树,生成编码:
A: 0, E: 10, R: 110, T: 1110, F: 11110, D: 11111
- 原字符串压缩率提升至约 60%。