2025.07.24 答疑工作
1 哈夫曼编码
给一个字符集 \(c_1, c_2, \cdots, c_n\) ,对应一个出现频率 \(w_1, w_2, \cdots, w_n\) 。
构造哈夫曼树
自底向上构造哈夫曼树,让 \(n\) 个字符作为叶子,第 \(i\) 个字符对应叶子的点权是 \(w_i\) ,一开始有 \(n\) 个没有父亲的节点。
每次选择两个频率出现最低的没有父亲的节点合并成一个新节点,新节点的点权是两个用于合并的点的点权之和。
合并的时候并不要求左右儿子的顺序。
最终可以合并成一棵树。
哈夫曼编码
已经得到哈夫曼树的情况下,我们采取左 \(0\) 右 \(1\) 或者左 \(1\) 右 \(0\) 的方式。(但不能混用,一般是左 \(0\) 右 \(1\) )
最终我们能得到 \(n\) 个字符的编码。
性质 1
任何一个编码都不是另一个编码的前缀。
- 证明。可以把哈夫曼树看成字典树。一个字符的编码是另一个字符的编码前缀,当且仅当另一个字符是这个字符在树上的子孙。
- 好处。哈夫曼编码的字符组合成一段二进制序列,存在解码歧义,当且仅当不确定某一段序列应该解码成一个字符,或者是作为另一个字符的编码前缀。
习题 1
对 "hello world" 进行哈夫曼编码(中间有个空格),需要多少个比特?
我们先写出每个字符和字符出现次数,利用这两个性质构造出哈夫曼树。
这个问题中,每个字符都占据一个比特,所以我们并不区分两个出现字数相同的字符。
我们让根节点深度为 \(0\) ,递归算出节点的深度 \(dep(u)\) ,需要的比特就是 \(\sum_{v \in \ \textbf{leaves}} dep_{v}\) 。
完全二叉树节点计算
问题:
给一棵完全二叉树,叶子个数是 \(L\) ,询问这颗完全二叉树总共有多少节点?
分析:
其实如果知道树高和最后一层有多少个叶子,就能回答问题。为了方便计算,我们设树层是从 \(0\) 层开始,我们分别设为 \(h, l\) 。
(实际上树层是从 \(1\) 层开始,如果询问 \(h\) ,我们回答 \(h + 1\) 即可。)
其中 \(h\) 是好算的,最小的 \(h\) 满足 \(2^{h} \geq L\) 就是所需要的值,这时候 \(h\) 可以计算成一个定值。
第 \(h-1\) 层的非叶子节点为 \(\lceil \frac{l}{2} \rceil\) ,那么这一层的叶子个数为 \(2^{h - 1}\) 。
于是 \(L = l + 2^{h - 1} - \lceil \frac{l}{2} \rceil\) ,这里这有 \(l\) 是未知数,这时候 \(l\) 也能计算出来。
前 \(h - 1\) 层的节点是满的,一共有 \(2^{h} - 1\) 个。最终总的节点个数是 \(2^{h} - 1 + l\) 。
实例:
比如 \(L = 124\) ,可以计算出 \(2^{6} = 64 < 124 \leq 2^{7} = 128\) ,于是 \(h = 7\) 。
已知 \(L = l + 2^{h - 1} - \lceil \frac{l}{2} \rceil\) 。
把 \(L = 124, h = 7\) 带入并整理,有 \(l - \lceil \frac{l}{2} \rceil = 60\) 。这个式子可以稍微二分算一下,有两个解,可得 \(l = \{ 120, 121 \}\) 。
最大节点数量是 \(2^{7} - 1 + 121 = 128 + 120 = 248\) 。
在 markdonw 中用 mermaid 画分支结构流程图
Mermaid 是一个基于 JavaScript 的图表生成工具,允许用户通过简单的文本语法(类似 Markdown)快速创建多种类型的图表,并直接嵌入网页或文档中。
- 声明代码块,声明语言是 mermain 。一般是在开头的三个反单引号后接 mermain ,比如 ```mermain 。
- 声明流程图 flowchart ,声明从上到下 TD (Top-Down)
下面是代码示例,并不声称语言是 mermain ,否则会进行编译。
flowchart TD
A[开始] --> B{是否按下空格键?}
B -- 是 --> C[小猫移动100步]
C --> D[小猫的颜色特效增加25]
D --> E[结束]
B -- 否 --> E
- [] 是矩形;{} 是菱形,表示条件选择;不加括号是文字;
- -- 是曲线;--> 是箭头;
- A,B,C,D 是块编号,编号唯一,可以通过编号定位到块。
证明“\(k\) 个数 \(2^{0}, 2^{1}, \cdots, 2^{k-1}\) ,能恰好组合出 \([0, 2^{k} - 1]\) 的所有整数”。
-
引理 1 . \(\forall x \in [0, 2^{k} - 1]\) 都能被 \(2^{0}, 2^{1}, \cdots, 2^{k - 1}\) 组合。
证明:
\(\forall n \in [0, 2^{k} - 1]\) 都可以写成二进制的多项式表示,即 \(n = \sum_{i = 0}^{k-1} a_i \cdot 2^i, \ s.t. \ a_i \in \{0,1\}\) 。
我们可以在 \(2^{0}, 2^{1}, 2^{2}, \cdots, 2^{k}\) 的 \(k\) 个数中,选择加入或不加入答案,凑出 \(n\) 。 -
引理 2 . \(2^{0}, 2^{1}, 2^{2}, \cdots, 2^{k - 1}\) 这 \(k\) 个数能够组成的所有子集的集合大小是 \(2^{k}\) 个。
证明:
\(k\) 个元素能够组成的所有子集的集合大小,可以看作 \(k\) 个数中任选 \(0, 1, \cdots, k\) 个数一共有多少种组合,总组合数就是就是集合大小。
\(\sum_{i = 0}^{k} \binom{k}{i} = 2^{k}\) 看起来不容易直接算,我们可以借助二项式定理 \((x+y)^{k} = \sum_{i = 0}^{k} \binom{k}{i} x^{k - i} y^{i}\) ,将 \(x=1,y=1\) 代入,那么就很显然,即 \((1 + 1)^{k} = \sum_{i=0}^{k} \binom{k}{i} 1^{k - i} 1^{i}\) 。
现在,我们严格定义 \(2^{0}, 2^{1}, 2^{2}, \cdots, 2^{k - 1}\) 这 \(k\) 个数中,任选 \(i(0 \leq i \leq k)\) 个数组成一个子集,这些子集的集合是 \(\mathbb{S}\) 。任意子集 \(x\) 满足 \(x \in \mathbb{S}\) 。
\(f\) 这个映射作用到 \(x\) 上表现为子集 \(x\) 中所有元素的和,即 \(f \circ x \rightarrow \sum_{y \in x} y\) 。
令 \(\mathbb{X}_{k} = \{0, 1, 2, \cdots, k\}\) 。考虑 \(\mathbb{S}\) 到 \(\mathbb{X}_{k}\) 的映射 $\mathbb{S} \overset{f}{\rightarrow} \mathbb{X}_k $ 。
引理 1 说明 \(f\) 是满射。
引理 2 说明 \(|\mathbb{S}| = |\mathbb{X}|_k\) 且 \(\mathbb{S}\) 是有限集。结合引理 1 ,排除了 \(f\) 为多对一的可能性(否则 \(|\mathbb{X}_n| > |\mathbb{S}|\) ),故只能一对一(因为是有限的集合)。即 \(f\) 是单射。
于是 \(f\) 是满射。
\(\square\)
少于 \(k\) 个数的组合,不足以对 \(\mathbb{X}_{k}\) 满射,所以 \(k\) 个数是最少的限制。
多于 \(k\) 个数的组合,对 \(\mathbb{X}_{k}\) 的映射不是单射,所以 \(k\) 个数是最多的限制。
朴素地讲,这意味着少于 \(k\) 个数的组合会漏算,多于 \(k\) 个数的组合如果不会漏算则一定会重算。
浙公网安备 33010602011771号