news 2026/8/3 5:39:00

【C++】CSP-J初赛——哈夫曼树与哈夫曼编码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C++】CSP-J初赛——哈夫曼树与哈夫曼编码

宇宙免责申明:

  1. 本文由deepseek进行了一些专业术语上的优化,可能会出现错误,如有错误,请私信联系.
  2. 本文的所有图均为本人手画,如果发现图中有错误,请私信联系.
  3. 内容为本人原创,未进允许,禁止搬运或转载。

一些关于密码学的知识

在开始我们今天的主要内容之前,我们需要了解一些关于密码学的知识,以便我们更好的理解。

  • 明文
    最原始、没有经过任何特殊处理,谁都能直接看懂的信息。

比如你给朋友写的小纸条:“放学后操场见”,这就是一条明文。

  • 密文
    为了隐藏信息的内容,按照某种规则把明文变换成另一种形式,使得不知道规则的人难以理解。

比如,我们约定把每个字都替换成它在字典里的后一个字,“放学后操场见”可能就变成了“放笔后笔场见”之类的乱码,这就成了一条密文。

将明文转换为密文的过程叫做加密,反过来,把密文恢复成明文的过程叫做解密。这套变换规则通常需要一把“钥匙”用来解密,也就是密钥

  • 加密:明文 → 密文
  • 解密:密文 → 明文
  • 密钥、密码本:加密与解密所依赖的“钥匙”

了解到这里,你可能会有疑问:这和哈夫曼树有什么关系?
实际上,哈夫曼树提供了一种十分精巧的编码方式。它不仅能极大压缩数据,还能根据字符出现的频率,生成独一无二的“密码本”。如果用这个密码本来对信息重新编码,在不知情的人眼里,它就是一团难以破解的01密文。有了这些概念垫底,我们就能更好地理解哈夫曼树在信息编码中的妙用了。


引入

一些问题

小明要参加考试,他的成绩很不好,而坐在他后面的小红是个学霸。小明花重金收买了小红并与她约定,小红做完试卷后用手指敲桌子,把选择题答案传给小明。

选择题只有 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)

要搞懂哈夫曼树,得先明白什么叫“带权路径长度”。在一棵二叉树里,每个叶子节点都有一个权值(比如字符的出现次数),从根走到这个叶子经过的边数就是它的路径长度。把一个叶子的权值乘上它的路径长度,再把所有叶子的这个乘积加起来,得到的就是整棵树的带权路径长度,记作 WPL:

WPL=∑i=1nwi×liWPL = \sum_{i=1}^{n} w_i \times l_iWPL=i=1nwi×li

其中wiw_iwi是第iii个叶子的权值,lil_ili是它的深度(到根经过的边数)。哈夫曼树的目标,就是让这个 WPL 变得最小。怎么做到呢?思路很直接:让出现次数多(权值大)的叶子离根近一点,出现次数少(权值小)的叶子离根远一点。这样,权重大的乘上小的深度,总和自然就最小了。

哈夫曼树的构建

构建哈夫曼树是一个“贪心”的过程,每次挑最小的两棵树合并,自底向上长成一棵完整的大树。具体步骤如下:

  1. 把每个带权值的节点都看作一棵只含一个根节点的二叉树,所有树组成一片森林。
  2. 在森林中,每次选出根节点权值最小的两棵树,将它们合并成一棵新树。新树的根节点权值,就是这两棵子树权值之和,左右孩子分别就是这两棵子树。
  3. 把新树放回森林,重复第 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)34(D)
  • 第2次合并:现在最小的两个是34(D),合并成权值为777的节点,左右孩子是刚才的3和 D。
    森林变为:5(A)7
  • 第3次合并:只剩下两棵树5(A)7,合并成权值为121212的根节点。哈夫曼树建成!

得到的树结构:

现在计算一下这棵哈夫曼树的带权路径长度 WPL:

WPL=5×1+4×2+1×3+2×3=5+8+3+6=22WPL = 5 \times 1 + 4 \times 2 + 1 \times 3 + 2 \times 3 = 5 + 8 + 3 + 6 = 22WPL=5×1+4×2+1×3+2×3=5+8+3+6=22

如果小明当初采用最简单的等长编码(每个选项用 2 位二进制表示,相当于固定路径长度为 2),总权值路径长度会是(5+1+2+4)×2=24(5+1+2+4) \times 2 = 24(5+1+2+4)×2=24。很显然,22 < 24,哈夫曼树确实让整体的“传输负担”变轻了。这也就为后面生成最优的哈夫曼编码打下了基础。

「注」:当然,在哈夫曼树并不是唯一的,叶子节点在同一层的位置是可以进行无数次调换的,因而每一组数据对应的哈夫曼树就有很多种,所以,当你在做题时发现没有选项能与你答案对应上时,不妨换一种画法。当然,哈夫曼树与哈夫曼编码的几个唯一和不唯一在后文会重新强调一遍,这里旨在解答读者在阅读时可能会遇到的问题。

哈夫曼编码

哈夫曼编码是在哈夫曼树的基础上进行的编码操作,它有好多套编码逻辑,这里简单介绍几种:

  • 左1右0
  • 左0右1

在图上则表示为:

在图片中,我们采用了左1右0的编码方案。于是,我们便很容易就能得到这些数据的的哈夫曼编码:

5 的编码为 1011
9 的编码为 1010
12的编码为 111
13的编码为 110
16的编码为 100
45的编码为 0

可以看到,权值最大的 45 分到了最短的编码0,而权值较小的 5 和 9 分到了较长的编码。这正是哈夫曼编码的精髓——频率越高,编码越短

回到我们小明的例子。按照上一节构建好的哈夫曼树,如果我们采用“左0右1”的方案,A、B、C、D 的编码就是:

  • A(权值 5):0
  • D(权值 4):10
  • B(权值 1):110
  • C(权值 2):111

现在小明再让小红用这套编码敲桌子传递答案,情况就完全不同了。假设答案是“A、D、C、A”,对应编码就是0101110,连在一起敲出来是0101110。小明按编码表去切分,0只能是 A,接下来10只能是 D,再往后111只能是 C,最后0又是 A,清清楚楚,不会有任何歧义。

这就是哈夫曼编码的两个关键优势:

无前缀歧义——每一个编码都不是另一个编码的前缀。因为所有字符都在叶子节点上,编码对应的路径走到叶子就停止,绝对不会走到半路碰上另一个字符,解码时边界非常清晰。

总长度最短——数学上可以证明,哈夫曼编码是所有前缀编码中,带权路径长度最小的。对于小明来说,这意味着在保证不出错的前提下,整场考试敲桌子的总次数降到了最低。

理解了哈夫曼树和哈夫曼编码,再回头看 CSP-J 的考题,无非就是手动构建哈夫曼树、计算带权路径长度、或者根据给定的树写出编码。只要记住“每次挑两个最小的合并”这个核心步骤,这些题目都能迎刃而解。

唯一和不唯一

在学习完了上面所有内容后,下面我们来把“唯一”和“不唯一”的地方彻底理清楚。

不唯一
树的结构形态不同

哈夫曼树的表现形式并不唯一。原因在于构建过程中的“选择”上。

回忆一下构建步骤:每次从森林中选出两个权值最小的根节点合并。问题就出在这里——如果森林中存在多个权值相同的节点,或者新合并出来的节点权值和森林中原有的某个节点权值相同,那么选谁、谁当左孩子、谁当右孩子,都会影响最终树的样子。

所以:只要在合并过程中出现了相等的权值,就可能产生不同的哈夫曼树结构。

同一棵树的编码方案不同

即便哈夫曼树的结构固定下来,编码方案也不唯一。前面提到过,既可以“左 0 右 1”,也可以“左 1 右 0”。这两种方案得到的编码表恰好是“翻转”关系,但本质上都是正确的前缀编码。

另外,合并时如果两个子树权值相同,谁当左孩子、谁当右孩子都可以,交换左右孩子后,编码也会随之改变。

唯一
最小带权路径长度(WPL)

虽然树的结构多种多样,但有一个东西是铁打不变的,那就是最小带权路径长度 WPL

不管你在权值相等时做了怎样的选择,不管左右孩子怎么安排,最终所有合法哈夫曼树的 WPL 值一定相等,而且都是该组权值下能达到的最小值。这才是哈夫曼树最本质的属性。考试中如果让你算 WPL,大可放心,只要构建过程遵循“每次取最小两个合并”的原则,最终算出的结果就是唯一的标准答案。

各字符的编码长度

这一点很多同学容易忽略。虽然编码本身(具体是011还是100)会因为左右分支约定不同而变化,但每个字符的编码长度(即它在树中的深度)在所有等价的哈夫曼树中是一致的

也就是说,在给定频率分布下,出现最频繁的字符一定拿到最短的编码,出现最少的字符一定拿到最长的编码,而且这些编码的位数是确定不变的。所以,如果题目只问你某个字符的编码是几位,答案也是唯一的。

小结
项目唯一?
哈夫曼树的形态结构不唯一(权值相等时选择不同)
具体编码值(0/1 序列)不唯一(左右分支约定可交换)
最小带权路径长度(WPL)唯一
每个字符的编码长度唯一

搞清楚这几点,以后再遇到关于哈夫曼树“是不是唯一”的问题,思路就不会乱了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/3 5:37:44

从新手到专家:工程师个人成长管理的系统化实践

1. 项目概述&#xff1a;个人成长管理的本质十年前我刚入行时&#xff0c;总以为技术能力就是一切。直到连续三个项目因为沟通问题搞砸后&#xff0c;才意识到个人管理远比想象中复杂。真正的专业成长&#xff0c;是技术硬实力与管理软技能的螺旋上升。"个人管理&#xff…

作者头像 李华
网站建设 2026/8/3 5:33:17

雷达降水测量:从Z-R关系到双偏振技术的原理与应用

1. 从“看见”到“算清”&#xff1a;雷达降水测量的核心价值在气象、水文、防灾减灾这些领域&#xff0c;我们经常听到“雷达回波图”&#xff0c;看到屏幕上那些五彩斑斓的色块。对于很多朋友来说&#xff0c;这可能只是一个“雨下得大不大”的直观参考。但作为一个和气象数据…

作者头像 李华
网站建设 2026/8/3 5:30:53

AI编程开发小程序:商业潜力与技术实践

1. AI编程开发小程序的商业潜力解析"用AI工具开发小程序月入十万"这个说法最近在开发者圈子流传甚广。作为从业十年的全栈工程师&#xff0c;我完整经历过从手工编码到AI辅助开发的整个技术演进过程&#xff0c;可以负责任地说&#xff1a;这个数字并非天方夜谭&…

作者头像 李华
网站建设 2026/8/3 5:29:43

ERP系统核心模块与技术架构全解析:从功能到实施的完整指南

1. 从“成分”视角重新审视ERP&#xff1a;它到底是什么&#xff1f;当我们在谈论ERP时&#xff0c;常常会陷入一个误区&#xff1a;把它看作一个单一的、庞大的软件系统。这种认知就像把一辆汽车仅仅看作一个“铁盒子”&#xff0c;而忽略了其内部的发动机、变速箱、底盘和电子…

作者头像 李华
网站建设 2026/8/3 5:29:32

1.69英寸SPI LCD驱动全解析:从硬件选型到DMA优化实战

1. 项目概述&#xff1a;为什么是1.69英寸SPI LCD&#xff1f;如果你正在为一个嵌入式项目寻找一块小巧、省电、驱动简单的显示屏&#xff0c;那么1.69英寸的SPI接口LCD绝对是一个值得深入研究的选项。它不像那些动辄5寸、7寸的“大家伙”需要复杂的RGB接口和高速内存&#xff…

作者头像 李华
网站建设 2026/8/3 5:28:24

USB端点与管道:数据通信的核心机制解析

1. USB端点与管道&#xff1a;数据通信的毛细血管系统当我们将U盘插入电脑时&#xff0c;那个小小的USB接口背后其实运行着一套精密的通信机制。作为硬件开发者&#xff0c;我经常需要与USB协议打交道&#xff0c;而端点和管道正是这套体系中最基础却最容易被忽视的核心概念。它…

作者头像 李华