
宇宙免责申明:本文由deepseek进行了一些专业术语上的优化,可能会出现错误,如有错误,请私信联系.本文的所有图均为本人手画,如果发现图中有错误,请私信联系.内容为本人原创未进允许禁止搬运或转载。一些关于密码学的知识在开始我们今天的主要内容之前我们需要了解一些关于密码学的知识以便我们更好的理解。明文最原始、没有经过任何特殊处理谁都能直接看懂的信息。比如你给朋友写的小纸条“放学后操场见”这就是一条明文。密文为了隐藏信息的内容按照某种规则把明文变换成另一种形式使得不知道规则的人难以理解。比如我们约定把每个字都替换成它在字典里的后一个字“放学后操场见”可能就变成了“放笔后笔场见”之类的乱码这就成了一条密文。将明文转换为密文的过程叫做加密反过来把密文恢复成明文的过程叫做解密。这套变换规则通常需要一把“钥匙”用来解密也就是密钥。加密明文 → 密文解密密文 → 明文密钥、密码本加密与解密所依赖的“钥匙”了解到这里你可能会有疑问这和哈夫曼树有什么关系实际上哈夫曼树提供了一种十分精巧的编码方式。它不仅能极大压缩数据还能根据字符出现的频率生成独一无二的“密码本”。如果用这个密码本来对信息重新编码在不知情的人眼里它就是一团难以破解的0和1密文。有了这些概念垫底我们就能更好地理解哈夫曼树在信息编码中的妙用了。引入一些问题小明要参加考试他的成绩很不好而坐在他后面的小红是个学霸。小明花重金收买了小红并与她约定小红做完试卷后用手指敲桌子把选择题答案传给小明。选择题只有 A、B、C、D 四个选项他们定了一套最简单的暗号敲 1 下 A敲 2 下 B敲 3 下 C敲 4 下 D考试刚开始小明就发现了两个大问题第一容易暴露。碰上连续几道题都选 C 或 D小红需要不停地敲动静太大很容易被老师发现。第二容易混淆。小红想传答案 “BA”——先敲 2 下表示 B再敲 1 下表示 A。可小明耳朵里听到的是连续 3 下他以为答案是 C之所以会搞错是因为 “B” 的编码“敲 2 下”和 “A” 的编码“敲 1 下”混在一起根本无法分清边界。本质上这是一个编码成了另一个编码的前缀导致解码出现歧义。那么有没有一种编码方法既能让总敲击次数尽量少又绝不会有前缀歧义呢哈夫曼树正是用来解决这类问题的完美工具。它能根据选项出现的频率自动生成一套最优的、绝不会产生歧义的编码方案。接下来我们就看看它是怎么做到的。哈夫曼树与哈夫曼编码哈夫曼树定义哈夫曼树又称为最优二叉树是一种带权路径长度最短的二叉树。那他具体长啥样呢长这样数据为5,9,16,12,13,45要搞懂哈夫曼树得先明白什么叫“带权路径长度”。在一棵二叉树里每个叶子节点都有一个权值比如字符的出现次数从根走到这个叶子经过的边数就是它的路径长度。把一个叶子的权值乘上它的路径长度再把所有叶子的这个乘积加起来得到的就是整棵树的带权路径长度记作 WPLWPL∑i1nwi×liWPL \sum_{i1}^{n} w_i \times l_iWPLi1∑nwi×li其中wiw_iwi是第iii个叶子的权值lil_ili是它的深度到根经过的边数。哈夫曼树的目标就是让这个 WPL 变得最小。怎么做到呢思路很直接让出现次数多权值大的叶子离根近一点出现次数少权值小的叶子离根远一点。这样权重大的乘上小的深度总和自然就最小了。哈夫曼树的构建构建哈夫曼树是一个“贪心”的过程每次挑最小的两棵树合并自底向上长成一棵完整的大树。具体步骤如下把每个带权值的节点都看作一棵只含一个根节点的二叉树所有树组成一片森林。在森林中每次选出根节点权值最小的两棵树将它们合并成一棵新树。新树的根节点权值就是这两棵子树权值之和左右孩子分别就是这两棵子树。把新树放回森林重复第 2 步直到森林里只剩下一棵树。这最后剩下的树就是哈夫曼树。回到小明的例子还记得小明和小红敲桌子的暗号吗假设考试选择题的四个选项 A、B、C、D 在试卷中出现的频率统计出来分别为 5、1、2、4。我们就拿这些频率作为权值亲手建一棵哈夫曼树。初始值5(A)、1(B)、2(C)、4(D)第1次合并最小的两个是1(B)和2(C)合并成一个权值为333的新节点左右孩子是 B 和 C。此时森林变成5(A)、3、4(D)第2次合并现在最小的两个是3和4(D)合并成权值为777的节点左右孩子是刚才的3和 D。森林变为5(A)、7第3次合并只剩下两棵树5(A)和7合并成权值为121212的根节点。哈夫曼树建成得到的树结构现在计算一下这棵哈夫曼树的带权路径长度 WPLWPL5×14×21×32×3583622WPL 5 \times 1 4 \times 2 1 \times 3 2 \times 3 5 8 3 6 22WPL5×14×21×32×3583622如果小明当初采用最简单的等长编码每个选项用 2 位二进制表示相当于固定路径长度为 2总权值路径长度会是(5124)×224(5124) \times 2 24(5124)×224。很显然22 24哈夫曼树确实让整体的“传输负担”变轻了。这也就为后面生成最优的哈夫曼编码打下了基础。「注」当然在哈夫曼树并不是唯一的叶子节点在同一层的位置是可以进行无数次调换的因而每一组数据对应的哈夫曼树就有很多种所以当你在做题时发现没有选项能与你答案对应上时不妨换一种画法。当然哈夫曼树与哈夫曼编码的几个唯一和不唯一在后文会重新强调一遍这里旨在解答读者在阅读时可能会遇到的问题。哈夫曼编码哈夫曼编码是在哈夫曼树的基础上进行的编码操作它有好多套编码逻辑这里简单介绍几种左1右0左0右1在图上则表示为在图片中我们采用了左1右0的编码方案。于是我们便很容易就能得到这些数据的的哈夫曼编码5 的编码为 10119 的编码为 101012的编码为 11113的编码为 11016的编码为 10045的编码为 0可以看到权值最大的 45 分到了最短的编码0而权值较小的 5 和 9 分到了较长的编码。这正是哈夫曼编码的精髓——频率越高编码越短。回到我们小明的例子。按照上一节构建好的哈夫曼树如果我们采用“左0右1”的方案A、B、C、D 的编码就是A权值 50D权值 410B权值 1110C权值 2111现在小明再让小红用这套编码敲桌子传递答案情况就完全不同了。假设答案是“A、D、C、A”对应编码就是0、10、111、0连在一起敲出来是0101110。小明按编码表去切分0只能是 A接下来10只能是 D再往后111只能是 C最后0又是 A清清楚楚不会有任何歧义。这就是哈夫曼编码的两个关键优势无前缀歧义——每一个编码都不是另一个编码的前缀。因为所有字符都在叶子节点上编码对应的路径走到叶子就停止绝对不会走到半路碰上另一个字符解码时边界非常清晰。总长度最短——数学上可以证明哈夫曼编码是所有前缀编码中带权路径长度最小的。对于小明来说这意味着在保证不出错的前提下整场考试敲桌子的总次数降到了最低。理解了哈夫曼树和哈夫曼编码再回头看 CSP-J 的考题无非就是手动构建哈夫曼树、计算带权路径长度、或者根据给定的树写出编码。只要记住“每次挑两个最小的合并”这个核心步骤这些题目都能迎刃而解。唯一和不唯一在学习完了上面所有内容后下面我们来把“唯一”和“不唯一”的地方彻底理清楚。不唯一树的结构形态不同哈夫曼树的表现形式并不唯一。原因在于构建过程中的“选择”上。回忆一下构建步骤每次从森林中选出两个权值最小的根节点合并。问题就出在这里——如果森林中存在多个权值相同的节点或者新合并出来的节点权值和森林中原有的某个节点权值相同那么选谁、谁当左孩子、谁当右孩子都会影响最终树的样子。所以只要在合并过程中出现了相等的权值就可能产生不同的哈夫曼树结构。同一棵树的编码方案不同即便哈夫曼树的结构固定下来编码方案也不唯一。前面提到过既可以“左 0 右 1”也可以“左 1 右 0”。这两种方案得到的编码表恰好是“翻转”关系但本质上都是正确的前缀编码。另外合并时如果两个子树权值相同谁当左孩子、谁当右孩子都可以交换左右孩子后编码也会随之改变。唯一最小带权路径长度WPL虽然树的结构多种多样但有一个东西是铁打不变的那就是最小带权路径长度 WPL。不管你在权值相等时做了怎样的选择不管左右孩子怎么安排最终所有合法哈夫曼树的 WPL 值一定相等而且都是该组权值下能达到的最小值。这才是哈夫曼树最本质的属性。考试中如果让你算 WPL大可放心只要构建过程遵循“每次取最小两个合并”的原则最终算出的结果就是唯一的标准答案。各字符的编码长度这一点很多同学容易忽略。虽然编码本身具体是011还是100会因为左右分支约定不同而变化但每个字符的编码长度即它在树中的深度在所有等价的哈夫曼树中是一致的。也就是说在给定频率分布下出现最频繁的字符一定拿到最短的编码出现最少的字符一定拿到最长的编码而且这些编码的位数是确定不变的。所以如果题目只问你某个字符的编码是几位答案也是唯一的。小结项目唯一哈夫曼树的形态结构不唯一权值相等时选择不同具体编码值0/1 序列不唯一左右分支约定可交换最小带权路径长度WPL唯一每个字符的编码长度唯一搞清楚这几点以后再遇到关于哈夫曼树“是不是唯一”的问题思路就不会乱了。