完整压缩与解压流程
本节阅读量:压缩函数完整实现
完整流程图
开始
↓
打开输入文件
↓
统计字符频率
↓
构建Huffman树
↓
生成编码表
↓
重新读取文件并编码
↓
计算Padding
↓
写入原始大小
↓
写入Huffman树
↓
写入Padding
↓
写入压缩数据
↓
关闭文件
↓
计算压缩率
↓
结束
代码详解
|
|
关键步骤说明
步骤2:统计字符频率
为什么需要两次读取文件?
- 第一次:统计字符频率,构建Huffman树
- 第二次:根据编码表进行编码
优化思路:可以一次性读取整个文件到内存,但会占用更多内存。
步骤7:写入原始大小
|
|
这里保存的是编码后的二进制位数,不是原始字符数!
步骤9:Padding计算
|
|
示例:
- 编码长度14位:
14 % 8 = 6,padding = 8 - 6 = 2 - 编码长度16位:
16 % 8 = 0,padding = 0
解压函数完整实现
完整流程图
开始
↓
打开压缩文件
↓
读取原始大小(位数)
↓
读取Huffman树
↓
读取Padding
↓
读取压缩数据
↓
将字节转换为二进制字符串
↓
遍历树解码
↓
输出到文件
↓
关闭文件
↓
结束
代码详解
|
|
关键算法:树遍历解码
这是解压的核心算法,也是最复杂的部分!
|
|
手动追踪解码过程
假设编码表:
a: 0
b: 10
c: 11
编码后的二进制串:01010011
解码过程:
初始化:current = root, decodedCount = 0
第1位 '0':
current = current->left
current 是叶子节点 → 输出 'a'
current = root
decodedCount = 1
第2位 '1':
current = current->right
current 不是叶子节点
第3位 '0':
current = current->left
current 是叶子节点 → 输出 'b'
current = root
decodedCount = 2
第4位 '1':
current = current->right
current 不是叶子节点
第5位 '0':
current = current->left
current 是叶子节点 → 输出 'b'
current = root
decodedCount = 3
第6位 '0':
current = current->left
current 是叶子节点 → 输出 'a'
current = root
decodedCount = 4
第7位 '1':
current = current->right
current 不是叶子节点
第8位 '1':
current = current->right
current 是叶子节点 → 输出 'c'
current = root
decodedCount = 5
解码完成,输出:"abbac"
压缩率计算
压缩率定义
压缩率 = (1 - 压缩后大小 / 原始大小) × 100%
含义:
- 正值:压缩后文件变小(压缩成功)
- 负值:压缩后文件变大(压缩失败)
- 零值:文件大小不变
代码实现
|
|
说明:
std::ios::ate:打开文件时定位到文件末尾tellg():返回当前文件指针位置(即文件大小)
为什么小文件可能变大?
原因分析:
假设有一个100字节的文件,包含100个不同字符:
- 原始大小:100字节
- Huffman树大小:约767字节(256个节点)
- 压缩数据:至少100字节
- 总大小:约867字节
- 压缩率:(1 - 867/100) × 100% = -767%
结论:Huffman编码对小文件不适用!
命令行接口
命令行参数处理
|
|
帮助信息
|
|
完整测试
|
|
小结与练习
本章总结:
-
压缩流程:
- 理解完整的压缩流程
- 掌握每个步骤的作用
- 能够独立实现压缩功能
-
解压流程:
- 理解完整的解压流程
- 掌握树遍历解码算法
- 能够独立实现解压功能
-
压缩率:
- 理解压缩率的计算
- 知道为什么小文件可能变大
- 理解Huffman编码的适用场景
练习1:手动解码
给定编码表:
a: 0
b: 10
c: 110
d: 111
手动解码二进制串:010100111110
练习2:压缩率计算
原始文件1000字节,压缩后800字节,计算压缩率。
练习3:优化思考
如何优化压缩流程,使其只需要读取一次文件?
练习4:错误处理
如果压缩文件损坏(如Huffman树部分缺失),解压时会发生什么?如何增加错误检测?
练习5:更近一步的优化
现在的算法,最好的情况下,用一个bit来表示一个字节,也就是说极限是7/8的压缩率。有没有办法进一步优化?
本节目录