c++怎么实现哈夫曼树编码压缩_c++ 字符频率统计与变长编码【案例】

发布时间 - 2025-12-30 00:00:00    点击率:
哈夫曼压缩核心是按频率构建最小堆二叉树并生成唯一变长编码:需以unsigned char统计0–255字节频次,自定义priority_queue升序比较器,合并时权重小者为左子树(编0),大者为右(编1),空文件或单字符需特判;编码表须按“字符+长度+对齐比特”二进制格式写入头部,并校验编码唯一性。

怎么用 C++ 构建哈夫曼树并生成变长编码

核心是:先统计字符频率,再用优先队列(最小堆)构建带权路径最短的二叉树,最后递归/迭代生成每个字符的编码。关键不在“写树”,而在「保证构建过程严格按权重合并」和「编码方向不能反」。

常见错误是把左子树当 1、右子树当 0(或反之),导致解码失败;或者没处理单字符输入(比如文件只含一个 'a'),堆里只剩一个节点时直接崩溃。

  • std::priority_queue 时必须自定义比较器,让它按 freq 升序——默认是大顶堆,得翻过来
  • 树节点建议用结构体而非类,避免虚函数开销;叶子节点需存原始字符(charint),内部节点设为 -10 标记
  • 编码生成推荐 DFS 递归:进左子树拼 "0",进右子树拼 "1";别用 BFS,容易乱序且难回溯路径
struct Node {
    int freq;
    char ch;
    Node* left;
    Node* right;
    Node(int f, char c) : freq(f), ch(c), left(nullptr), right(nullptr) {}
};
struct Compare {
    bool operator()(Node* a, Node* b) { return a->freq > b->freq; }
};
// 构建过程节选
std::priority_queue, Compare> pq;
// ... 插入所有叶子节点
while (pq.size() > 1) {
    Node* l = pq.top(); pq.pop();
    Node* r = pq.top(); pq.pop();
    Node* merged = new Node(l->freq + r->freq, '\0');
    merged->left = l;
    merged->right = r;
    pq.push(merged);
}

字符频率统计要注意哪些边界情况

不能简单用 std::map 然后 fstream.get() 逐字节读——遇到空字符 '\0'、换行符 '\n'、高位字节(如 UTF-8 中文)会截断或误判。实际压缩对象是字节流,不是“字符流”。

  • 必须以 unsigned char 视角读取文件,映射到 int 范围 [0, 255],用 std::array 统计最稳
  • 文件末尾的 EOF 不算有效字节,istream::get() 返回 int,需判断是否等于 EOF 再转 unsigned char
  • 若输入为空文件,频率数组全零,后续建树要提前检查 total_count == 0 并跳过压缩
std::array freq{};
std::ifstream fin("input.bin", std::ios::binary);
int byte;
while ((byte = fin.get()) != EOF) {
    freq[static_cast(byte)]++;
}

怎么把编码表高效存进压缩文件头部

不能直接写字符串如 "a:010\nb:11\n"——这本身就在膨胀数据。标准做法是:先写字符(1 字节),再写其编码长度(1 字节),最后写编码比特(按字节对齐,高位在前)。

例如字符 'x' 编码是 "1011"(4 位),就写:0x78('x' 的 ASCII)、0x040xB010110000,后 4 位补零凑满 1 字节)。解压时靠长度字段截取有效比特。

  • 编码长度超过 8 位?正常,哈夫曼树深度可能达 256,但实际英文文本一般 ≤ 32
  • 务必在头部末尾写一个结束标记(如 0xFF),否则解压器无法知道头在哪结束
  • 别用 std::string 拼接编码比特——它按字节存,而你需要按位写入,得手写 bit writer 类或用 std::vector(注意它不是容器,别用 data()

为什么压缩后文件反而变大了

小文件(

  • 测试时用 >10 KB 的纯英文文本(如《The Raven》),才能看到 30%~40% 压缩率
  • 如果源文件已用 gzip 压过,再套哈夫曼只会更大——熵已经极低,变长编码失去优势
  • 真正工程中不会单独用哈夫曼,而是作为 DEFLATE(zip)的后端;自己实现时,至少加一层游程编码预处理,对付重复字节

最容易被忽略的是:没做「编码唯一性校验」。两个不同字符生成相同编码(比如都成了 "0"),整个压缩就不可逆。构建完树必须遍历所有叶子,确认无重复编码串——哪怕只是调试时用 std::set<:string> 临时塞一遍。


# node  # 编码  # 字节  # 后端  # c++  # ios  # 解压  # stream  # 为什么  # EOF  # String  # Array  # 字符串  # 结构体  # 递归  # char  # int  # 虚函数  # fstream  #   # map  # 对象  # ASCII  # 子树  # 升序  # 英文  # 变长  # 自定义  # 时用  # 的是  # 成了  # 就在 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 如何在Windows服务器上快速搭建网站?  手机钓鱼网站怎么制作视频,怎样拦截钓鱼网站。怎么办?  Laravel如何实现全文搜索功能?(Scout和Algolia示例)  如何在橙子建站中快速调整背景颜色?  米侠浏览器网页图片不显示怎么办 米侠图片加载修复  三星网站视频制作教程下载,三星w23网页如何全屏?  Laravel怎么写单元测试_PHPUnit在Laravel项目中的基础测试入门  微信小程序 scroll-view组件实现列表页实例代码  怎么制作网站设计模板图片,有电商商品详情页面的免费模板素材网站推荐吗?  php485函数参数是什么意思_php485各参数详细说明【介绍】  Android自定义控件实现温度旋转按钮效果  PHP的CURL方法curl_setopt()函数案例介绍(抓取网页,POST数据)  Laravel如何创建自定义Facades?(详细步骤)  laravel怎么实现图片的压缩和裁剪_laravel图片压缩与裁剪方法  详解Oracle修改字段类型方法总结  JavaScript数据类型有哪些_如何准确判断一个变量的类型  Laravel如何配置Horizon来管理队列?(安装和使用)  详解Android图表 MPAndroidChart折线图  Windows10如何删除恢复分区_Win10 Diskpart命令强制删除分区  如何快速生成凡客建站的专业级图册?  Java类加载基本过程详细介绍  b2c电商网站制作流程,b2c水平综合的电商平台?  如何在 Pandas 中基于一列条件计算另一列的分组均值  如何在云主机上快速搭建网站?  如何基于PHP生成高效IDC网络公司建站源码?  如何在IIS7中新建站点?详细步骤解析  香港服务器网站推广:SEO优化与外贸独立站搭建策略  HTML5空格和nbsp有啥关系_nbsp的作用及使用场景【说明】  详解Android——蓝牙技术 带你实现终端间数据传输  Bootstrap整体框架之JavaScript插件架构  如何确保FTP站点访问权限与数据传输安全?  HTML5打空格有哪些误区_新手常犯的空格使用错误【技巧】  专业企业网站设计制作公司,如何理解商贸企业的统一配送和分销网络建设?  南京网站制作费用,南京远驱官方网站?  佛山网站制作系统,佛山企业变更地址网上办理步骤?  JavaScript Ajax实现异步通信  详解MySQL数据库的安装与密码配置  移动端脚本框架Hammer.js  如何正确下载安装西数主机建站助手?  laravel怎么在请求结束后执行任务(Terminable Middleware)_laravel Terminable Middleware请求结束任务执行方法  网站视频制作书签怎么做,ie浏览器怎么将网站固定在书签工具栏?  专业商城网站制作公司有哪些,pi商城官网是哪个?  开心动漫网站制作软件下载,十分开心动画为何停播?  Chrome浏览器标签页分组怎么用_谷歌浏览器整理标签页技巧【效率】  Laravel如何实现URL美化Slug功能_Laravel使用eloquent-sluggable生成别名【方法】  网站制作怎么样才能赚钱,用自己的电脑做服务器架设网站有什么利弊,能赚钱吗?  Laravel如何构建RESTful API_Laravel标准化API接口开发指南  Linux安全能力提升路径_长期防护思维说明【指导】  Laravel如何使用Blade组件和插槽?(Component代码示例)  EditPlus中的正则表达式实战(6)