前言
好久没写博客了,主要是各种事缠着,难以抽出时间。这两个月以来,由于项目需要,我也逼着自己学到了很多,什么java后台,web前端,还有万恶的OpenCV图形处理……,呵呵,全栈:( 。但对Android的学习我始终不肯放下。但是今天这篇博客不是关于Android的,而是算法的相关应用–哈夫曼压缩。这是数据结构与算法实验里面的一个项目,网上关于这方面的资料很多,但大多数博客都是随便讲讲然后扔下代码。同时有同学请教我,所以就有了写一篇关于这个知识点的高质量博文的想法。
你应该知道
读这篇博客前你应该掌握如下的基本知识:
- 最基本的常识,一个字节有8位,int一般占4个字节,即32位。
- vector动态数组的基本用法
- 利用FILE类对二进制文件的基本读写操作
- fgetc(fin);方法虽然返回的是int,但实际上是由一个字节转换而来的,所以其范围也是0~255;同样地,fputc(int,fout);方法也是一样,写入一个字节到二进制文件当中,所以传入的int的范围也在0~255。
- 计算机存储文件都是以二进制流的形式来存的,图片也不例外。
- 值得吐槽的是,C++读写操作的最小单位是字节,要想以bit为单位读写文件只能通过读写字节然后进行移位运算。Java就很人性化啦,提供了bit流的IO操作函数。
- 利用fwrite()、fread()方法可以将数据块读写文件,权值数组的读写就是这两个方法进行。这两个方法的使用请查阅文档。
- -
哈夫曼编码
其实哈夫曼编码并不是本篇的重点,所以下面我只进行粗略的讲述。
压缩的原理
计算机文件是由01串组成的。那么举个栗子,有一个文件,头几个二进制串:
01000010010011010001011011011111……..
那么,C++就是每8位(bit)来读,就是以字节为单位来读取,每个字节被转化成整型int。
读取代码如下:
int c;
vector<int> binaryData;
while (true) {
c = fgetc(fin);
if (feof(fin)) break;
weight[c]++;
binaryData.push_back(c);
cout<<c<<endl;
}
输出为:66 77 22 223 ………
这是定长的编码方式。而Huffman编码不定长的编码方式,是通过出现字节的频率的不同编程长度不同的01码字。假如,这里66这个字节出现了1万次,77这个字节出现了只5次,那我们当然想把66尽可能用短一点的码字来编,而77就用长一点的码字来编也无所谓,毕竟它出现的次数少。这样不就能有效地缩短了文件整体的bit数吗?
具体代码的实现
根据各个字节出现的频率(权值)来构建Huffman树离不开对权值进行排序。而我们发现,用最小堆来构建Huffman树是最优雅的方式了。
核心代码:
//传入权值数组形成最小堆
MinHeap heap(n, h);
HuffmanTreeNode *n1 = NULL;
HuffmanTreeNode *n2 = NULL;
HuffmanTreeNode *parent = NULL;
//进行n-1次操作后,堆已空,哈夫曼树构建完成
for (int i = 0; i < n - 1; i++) {
//从堆中取出最小两个的节点,
n1 = heap.pop();
n2 = heap.pop();
//new一个父节点,父节点的值为两个子节点的值之和
parent = new HuffmanTreeNode(n1->weight + n2->weight);
//连接刚才取出的两个节点,