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)快速创建多种类型的图表,并直接嵌入网页或文档中。

  1. 声明代码块,声明语言是 mermain 。一般是在开头的三个反单引号后接 mermain ,比如 ```mermain 。
  2. 声明流程图 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. 引理 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 . \(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\) 个数的组合如果不会漏算则一定会重算。

posted @ 2025-07-25 03:45  03Goose  阅读(37)  评论(0)    收藏  举报