5.6 哈夫曼树与并查集
带权路径长度与哈夫曼树的构造、前缀编码与哈夫曼编码;并查集的双亲表示、按大小合并与路径压缩。
哈夫曼树
带权路径长度
在许多应用中,树中结点常常被赋予一个表示某种意义的数值,称为该结点的权。从树的根到任意结点的路径长度(经过的边数)与该结点上权值的乘积,称为该结点的带权路径长度。树中所有叶结点的带权路径长度之和称为该树的带权路径长度,记为
式中, 是第 个叶结点所带的权值, 是该叶结点到根结点的路径长度。
在含有 个带权叶结点的二叉树中,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称最优二叉树。
(a、b、c、d 的权值分别为 7、5、2、4)可见,权值越大的叶结点离根越近,WPL 越小。
构造哈夫曼树
给定 个权值分别为 的结点,构造哈夫曼树的算法描述如下:
- 将这 个结点分别作为 棵仅含一个结点的二叉树,构成森林 F;
- 构造一个新结点,从 F 中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和;
- 从 F 中删除刚才选出的两棵树,同时将新得到的树加入 F 中;
- 重复步骤 2 和 3,直至 F 中只剩下一棵树为止。
哈夫曼编码
在数据通信中,若对每个字符用相等长度的二进制位表示,称这种编码方式为固定长度编码;若允许对不同字符用不等长的二进制位表示,则这种编码方式称为可变长度编码。可变长度编码比固定长度编码要好得多,其特点是对频率高的字符赋以短编码,而对频率较低的字符则赋以较长一些的编码,从而可以使字符的平均编码长度减短,起到压缩数据的效果。
若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码。前缀编码的解码很简单:因为没有一个编码是其他编码的前缀,所以识别出第一个编码,将它翻译为原字符,再对余下的编码文件重复同样的解码操作。
由哈夫曼树得到哈夫曼编码:将每个出现的字符当作一个独立的结点,其权值为它出现的频度(或次数),构造出对应的哈夫曼树。将字符的编码解释为从根至该字符的路径上边标记的序列,其中边标记为 0 表示”转向左孩子”,标记为 1 表示”转向右孩子”。由于字符都在叶结点上,哈夫曼编码一定是前缀编码。WPL 可视为最终编码得到的二进制编码长度。
并查集
并查集是一种简单的集合表示,它支持以下 3 种操作:
Initial(S):将集合 S 中的每个元素都初始化为只有一个单元素的子集合;Union(S, Root1, Root2):把集合 S 中的子集合 Root2 并入子集合 Root1。要求 Root1 和 Root2 互不相交,否则不执行合并;Find(S, x):查找集合 S 中单元素 x 所在的子集合,并返回该子集合的根结点。
通常用树的双亲表示作为并查集的存储结构,每个子集合以一棵树表示。所有表示子集合的树,构成表示全集合的森林,存放在双亲表示数组内。通常用数组元素的下标代表元素名,用根结点的下标代表子集合名,根结点的双亲域为负数(可设置为该子集合元素数量的相反数)。
1int Find(int S[], int x) {2 int root = x;3 while (S[root] >= 0) // 循环找到根4 root = S[root];5 while (x != root) { // 路径压缩(可选)6 int t = S[x];7 S[x] = root; // x 直接挂到根下8 x = t;9 }10 return root;11}12void Union(int S[], int Root1, int Root2) {13 if (Root1 == Root2) return;14 if (S[Root2] > S[Root1]) { // Root2 结点数更少15 S[Root1] += S[Root2]; // 累加结点总数16 S[Root2] = Root1; // 小树合并到大树17 } else {18 S[Root2] += S[Root1];19 S[Root1] = Root2;20 }21}
两种优化
| 实现 | Find 最坏时间 | Union 时间 | 说明 |
|---|---|---|---|
| 朴素 | 树可能退化为一条链 | ||
| 小树并入大树(按大小合并) | 可以证明树高不超过 | ||
| 按大小合并 + 路径压缩 | 约 | 约 | 增长极慢,通常 |
在动画中分别勾选、取消”小树并入大树”和”路径压缩”,对比最终森林的高度。
即时练习
- 1在有 n 个叶结点的哈夫曼树中,非叶结点的总数是( )。
- 2已知字符集 {a, b, c, d, e, f, g, h},若各字符的哈夫曼编码依次是 0100, 10, 0000, 0101, 001, 011, 11, 0001,则编码序列 0100011001001011110101 的译码结果是( )。
- 3下列选项给出的是从根分别到达两个叶结点路径上的权值序列,能属于同一棵哈夫曼树的是( )。
- 4设并查集用数组 S 表示,采用"小树并入大树"的合并方法,初始时 n 个元素各自成为一个集合。经过若干次合并后,任意一棵树的高度至多为( )。
- 5若度为 m 的哈夫曼树中,叶结点个数为 n,则非叶结点的个数为( )。
标记完成后,侧栏会显示进度。进度仅保存在本浏览器中。