在大屏或电脑上体验更佳——动画和操作区域更宽敞。
输入

哈夫曼编码

哈夫曼编码根据符号频率构建最优的二进制前缀码。每次合并权重最小的两棵树,让低频符号位于较深位置,高频符号使用较短编码。默认 ABRACADABRA 的载荷为 23 位,同符号集的定长码为 33 位。

摩尔斯码也给常见 E 一个点、Q 四个信号,但它依赖字符间停顿。普通比特流没有停顿,因此哈夫曼码要求任何符号码都不是另一个的前缀。把符号只放在树的叶子上,就能满足这一点。

哈夫曼编码如何工作

统计频率,每个符号作为带权单节点树放入最小优先队列。反复取出最轻的两棵,接到权重为两者之和的新父节点下,再把合并树放回队列,直到只剩一棵。

从根到各符号叶子,左边记 0、右边记 1,路径就是码字,深度就是码长。合并顺序决定哪些符号承担更长编码。

为什么最小两项可以先合并

取一棵最优前缀树中最深的一对兄弟叶子,把两个最低频符号交换到那里。低频符号加深所增加的成本,不超过高频符号变浅节省的成本,因此总成本不增加。于是至少存在一棵最优树,让这两个最低频符号成为兄弟。

把它们缩成权重之和的一个符号,重复同样论证,再展开。这个归纳过程证明每次最小合并安全。

同权重时,本课按创建顺序处理,让相同文本得到相同树。其他一致平局规则也可得到最优树,码字可能不同,最优总位数相同。

画布把优先队列画成从左到右排序的架子,以展示“取最小两项”。Python 实际使用 heapq,内部数组不是全排序;堆课程另行展示它的存储结构。

逐步演示

默认 ABRACADABRA,11 个字符、5 种符号。

  1. 计数:A 五次,B、R 各两次,C、D 各一次。
  2. 队列:按频率和创建顺序为 C(1) · D(1) · B(2) · R(2) · A(5)。
  3. 合并一:C、D 合为权重 2 的 [CD]。同为 2 时旧节点优先,得到 B(2) · R(2) · [CD](2) · A(5)。
  4. 合并二:B、R 合成 4,队列 [CD](2) · [BR](4) · A(5)。
  5. 合并三:CD、BR 合成 6,队列 A(5) · [CDBR](6)。
  6. 合并四:5 与 6 合并,根权重 11,等于文本长度。
  7. 编码:左 0、右 1。A 距根一边,码为 0;其余深度三,C=100、D=101、B=110、R=111。
  8. 输出:0 · 110 · 111 · 0 · 100 · 0 · 101 · 0 · 110 · 111 · 0,共 23 位。五种符号定长每个需 ⌈log₂ 5⌉ = 3 位,共 33 位,减少约 30%。五个 A 各从三位降到一位,省下十位。

画布每次取架子最左两树,琥珀色 min pair 括号标出它们。结尾绿柱 23、灰柱 33 比较载荷,不含码表。实际文件还需让解码器知道编码规则,这么短的文本中,表头可能超过节省的十位。

时间与空间复杂度

令 n 为文本长度,k 为不同符号数。

阶段常用计数模型下的成本
统计频率O(n)
堆上构树,k−1 次合并O(k log k)
访问树并分配路径O(k) 次节点访问
编码O(n) 次符号查询

常用固定字母表、码字操作按常数处理的模型下,总时间 O(n + k log k),树与频率表空间 O(k)。若逐位生成输出、复制全部码字,还要计入输出位数和码字总长度,不能把这些可变长数据当成免费。

合并次数固定为 k−1,压缩收益却由频率分布决定。在同一至少二符号字母表的二进制前缀码模型下,定长码也是合法候选,因此哈夫曼载荷不劣于它。能否省位,取决于最优平均码长是否低于 ⌈log₂ k⌉。

边界情况

  • 只有一种符号:没有合并,根也是叶子,路径为空。本实现用 path or "0" 赋一位码,避免按当前解码约定输出空串。其他格式也可另存长度,但必须有明确约定。
  • 两种符号:各一位,即使频率偏斜也如此。逐符号二进制前缀码不能给一个符号半位;算术编码可跨符号降低平均位数。
  • 与定长相同:Uniform 的四种等频符号各两位,总计 24 对 24。五种等频符号仍可省位,24 对 30;频率 3、3、2、2 虽不均匀,也可在 20 位打平。是否均匀不等于是否打平。
  • 极端偏斜:频率 1、2、4、8 可形成链式形状,低频码长到 k−1,但总量仍更优。Skewed 为 25 对 30。
  • 平局:创建顺序使本课可复现;其他选择可改形状和码字,不改最优总成本。

常见错误

  • 只排序一次后顺次配对。 合并父节点必须回队列,以新权重参与之后选择。
  • 按频率名次直接分配 0、1、01。 可能让一个码成为另一个的前缀,无法判断何时结束符号。需要保持前缀无歧义规则。
  • 忽略单符号约定。 本实现需要 path or "0",否则空路径无法按当前格式表达重复次数。
  • 只发送载荷,不提供编码规则。 解码器需树、码表或可复建的频率与规则。实际压缩比要计入表头,短文本不代表会省文件空间。
  • 编码器与解码器平局不一致。 两棵树都可能最优,却映射到不同码字。靠频率重建时,必须采用同一平局规则。