C++ 实现哈夫曼树最优二进制编码构建算法核心源码实现方案贪心【源码】

冬伟小哥_4403

冬伟小哥_4403

2026-05-14

983人浏览

原创

不能直接用std::priority_queue存treenode*,因默认比较器按地址而非权重排序,需自定义小根堆比较器:return a->weight > b->weight。

c++ 实现哈夫曼树最优二进制编码构建算法核心源码实现方案贪心【源码】

为什么不能直接用 std::priority_queue 存 TreeNode* 就完事?

因为默认比较器对指针比的是地址值,不是节点权重。你 push 进去的两个权重都是 5 的节点,std::priority_queue 可能按内存地址乱序弹出,导致合并顺序错误,最终编码长度非最优。

必须自定义比较器,让小根堆按 weight 升序排列:

struct CompareNode {
    bool operator()(const TreeNode* a, const TreeNode* b) const {
        return a->weight > b->weight; // 注意是 >
    }
};
std::priority_queue<treenode std::vector>, CompareNode> pq;
</treenode>
  • 别写成 a->weight weight,那是大根堆逻辑
  • 如果用 lambda,得用 decltype 配合 auto 声明,但 C++11 不支持模板参数推导,建议老实用 struct
  • 节点析构要小心:所有 new 出来的节点,最后得统一 delete,否则内存泄漏

构建哈夫曼树时,怎么保证叶子节点对应原始字符且不丢失频次信息?

关键在初始化叶节点阶段:每个字符必须绑定唯一频次,并作为叶子节点的 weight 和 ch(或 symbol)字段存下来。中间合并节点的 ch 字段应置为 '\0' 或 -1,避免误判。

常见错误是把字符数组和频次数组分开存,后续建树时索引错位。推荐封装成结构体:

struct SymbolFreq {
    char ch;
    int freq;
    SymbolFreq(char c, int f) : ch(c), freq(f) {}
};
  • 输入频次必须 > 0;若某字符频次为 0,直接跳过,不创建节点
  • 只有一个字符时(如全 'a'),哈夫曼树退化为单节点,编码结果应为 "0"(而非空字符串),否则解码端无法处理
  • 字符集含 '\0' 时,ch 字段不能用于判断是否为叶子——改用 is_leaf() 成员函数更安全

生成编码字符串时,递归遍历容易栈溢出?用迭代 DFS 更稳

深度优先递归写法简洁,但当字符数上万、树高可能达 O(n) 时,容易触发栈溢出(尤其 Windows 默认栈仅 1MB)。改用显式栈 + 父指针标记路径更可控。

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载

核心思路:每个节点压栈时,同时记录到达它的编码位(0 或 1),拼接路径字符串:

std::stack<:pair std::string>> stk;
stk.push({root, ""});
while (!stk.empty()) {
    auto [node, code] = stk.top(); stk.pop();
    if (!node->left && !node->right) { // 叶子
        codes[node->ch] = code;
    }
    if (node->right) stk.push({node->right, code + "1"});
    if (node->left)  stk.push({node->left,  code + "0"});
}
</:pair>
  • 注意左右子树入栈顺序:先 right 后 left,才能保证左 0 / 右 1 的约定不翻转
  • 不用 std::string 拼接高频场景?可改用 std::vector<char></char> 缓存路径,最后构造 string,减少临时对象
  • 如果只要编码长度不要具体码字,可只存深度,省掉字符串操作

贪心选择性质在哪儿体现?为什么每次合并最小两项就一定最优?

体现在构建过程每一步都取当前可用的两个最小权重节点合并——这确保了高频字符靠近根(编码短),低频字符沉底(编码长)。数学上可证:若存在更优解,其必含某次合并未选最小两项,可通过交换调整得到不更差的解,矛盾。

实操中要注意边界验证:

  • 输入至少两个不同符号才需合并;单符号直接返回 "0"
  • 合并后新节点权重 = 左右子权和,这个值必须参与后续比较——漏加会导致后续选错
  • 浮点频次(如概率)需转整型放大,否则 double 比较易因精度失效,破坏贪心前提

真正容易被忽略的是:哈夫曼编码只保证**带权路径长度最小**,不保证字典序或前缀码“看起来更短”。比如字符 'a'(freq=1)、'b'(freq=2)、'c'(freq=3),生成的编码可能是 "11", "10", "0" —— 最短码给了最高频,但字符串长度不是单调的。别拿输出码长序列去反推算法错了。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关专题

更多
c++和c语言的区别有哪些
c++和c语言的区别有哪些

c++和c语言的区别:1、面向对象编程(OOP)支持不同;2、新增特性不同;3、标准库不同;4、编译方式不同;5、命名空间不同等等。想了解更多c++的相关内容,可以阅读本专题下面的文章。

2024.03.14

2248

9

c++和python学习顺序推荐
c++和python学习顺序推荐

一般建议先学习C++,再学习Python,因为这样可以逐步从较为底层的编程语言向更高级的语言过渡。想了解更多python的相关内容,可以阅读本专题下面的文章。

2024.03.14

1019

6

python和c++学习性价比分析
python和c++学习性价比分析

Python易于学习,广泛应用于Web开发、数据科学和人工智能等领域,但性能较低。C语言性能高,适用于对性能要求较高的场景,如游戏开发和系统编程,但学习曲线陡峭,错误处理复杂。想了解更多python的相关内容,可以阅读本专题下面的文章。

2024.03.14

427

5

c语言和c++一样吗
c语言和c++一样吗

c语言和c++是两种不同的编程语言,虽然有相似之处,但存在显著差异。c语言专注于过程式编程和系统级开发,以简洁、高效著称。c++作为c语言的超集,引入了面向对象编程,增强了代码组织和管理能力,但学习曲线也更陡峭。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.03.14

327

5

c语言和c++先学哪个好
c语言和c++先学哪个好

初学者选择学习c语言还是c++语言,需要根据个人学习目标、背景以及编程兴趣和预期应用方向来决定。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.03.14

406

5

c语言和c++的区别和联系
c语言和c++的区别和联系

c语言和c++是计算机科学领域应用广泛的编程语言。虽然它们有着相似的基础,但它们在语言类型、语法功能和内存管理方面存在着显著差异。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.03.14

580

5

c++软件中文更改教程
c++软件中文更改教程

对于 ide,可通过打开设置,找到语言设置,选择中文,并保存更改。对于非 ide 应用程序,可查找设置或选项,选择语言设置,更改为中文,并保存更改。想了解更多c++的相关内容,可以阅读本专题下面的文章。

2024.03.21

1409

9

python和java和c++学习性价比分析
python和java和c++学习性价比分析

Python以其易学性、丰富的库和活跃的社区而著称,适合数据科学、人工智能和Web开发。Java以其跨平台性、企业级应用开发和Android应用开发而闻名。C++以其底层控制能力、高效性能和游戏开发而著称。选择哪种语言取决于个人兴趣、职业方向和特定需求。想了解更多python和java和c++的相关内容,可以阅读本专题下面的文章。

2024.03.22

1217

7

c++和c语言学习顺序推荐
c++和c语言学习顺序推荐

对于初学者,建议先学习C语言,掌握编程基础后再转入C++,便于理解面向对象编程概念。有编程经验者可直接学习C++,快速接触高级编程技术。想了解更多c++和c语言的相关内容,可以阅读本专题下面的文章。

2024.03.25

1325

9

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
LLVM Programmer’s Manual
LLVM Programmer’s Manual

共0课时 | 0人学习

Fitten Code官方手册
Fitten Code官方手册

共0课时 | 0人学习

CodeGeex使用手册
CodeGeex使用手册

共0课时 | 0人学习