在上一篇博客中我们介绍了哈夫曼(huffman)树
本文将介绍一下如何用huffman树实现文件压缩。
首先,我们将文件压缩这个项目分为五个步骤:
1. 统计字符出现的次数。
2. 构建哈夫曼树
3. 生成哈夫曼编码
4. 压缩文件
5. 解压缩文件
以上就是文件压缩的基本思想,我们不难发现出现的越频繁,次数越多的字符,它的huffman编码就会越短,并且占用的空间也会越少,如果我们压缩的都是出现率特别高的重复字符,那么我们的压缩效果就会越好,相反,出现的字符越多并且很少有重复的那么压缩效率就会越低。
我们在写这五个步骤代码之前,需要先定义一个结构体,该结构是项目要用到的自定义类型,并且里面存放了字符,字符出现的次数,以及哈夫曼编码:
struct CharInfo
{
char _ch;//字符
long long _count;//出现的次数
string _code;//huffman编码
bool operator!=(const CharInfo& info)
{
return _count != info._count;
}
bool operator<(const CharInfo& info)
{
return _count < info._count;
}
CharInfo operator+(const CharInfo& info)
{
CharInfo tmp;
tmp._count = _count + info._count;
return tmp;
}
};
那么我们就来具体的分析一下这五个步骤:
1.统计字符出现的次数。
我们要统计出要压缩的文件,每个字符出现的次数。首先我们要打开文件,并且开始读,将每个字符保存在_info数组中
代码:
//打开文件
FILE* fout = fopen(file, "rb");
assert(fout);
//统计字符出现的次数
char ch = fgetc(fout);
while (!feof(fout))
{
_infos[(unsigned char)ch]._count++;
ch = fgetc(fout);//读当前字符,并且指针移向下一个字符
}
2. 构建huffman树
//2,构建哈夫曼树
CharInfo invalid;
invalid._count = 0;
//构建哈夫曼树的函数在上一篇博客有提到
HuffmanTree<CharInfo> tree(_infos, 256, invalid);
3.生成哈夫曼编码
在这里有一个小小的问题:生成的hufffman编码存放在哪里?我们应该存在数组中,在数组中查找的快,所有我们在最开始的结构体中加入了string code,之所以我们弄为string是因为,静态的开辟太小,存不下,静态的开辟太大,多余的话又浪费了,所以使用string
生成哈夫曼编码有两种方法:
方法一:三叉链方法
如果这个树是三叉链,那么我们找到所有的叶子节点,从叶子结点往根节点走,由于这样出来的编码是倒着的,所以最后我们需要把保存下来的编码逆置一下
逆置的方法:
1. 头插(缺点:效率低,每次都需要挪动元素,效率为2*N(N-1))
2. 逆置函数rereverse()(效率高,推荐使用,效率为二分之N)
3. for循环
在本文我们使用的是函数的方法
void GetHuffmanCode(Node* root)
{
if (root == NULL)
{
return;
}
if (root->_left == NULL&&root->_right == NULL)
{
string &code = _infos[(unsigned char)root->_w._ch]._code;
Node* cur = root;
Node* parent = cur->_parent;
while (parent)
{
//向左走0
if (cur == parent->_left)
{
code.push_back('0');
}
//向右走1
else if (cur == parent->_right )
{
code.push_back('1');
}
else
{
break;
}
cur = parent;
parent = cur->_parent;
}
reverse(code.begin(), code.end());
}
GetHuffmanCode(root->_left);
GetHuffmanCode(root->_right);
}
方法二:不用三叉链
我们多传入一个函数参数code
void GetHuffmanCode(Node* root, string code)
{
if (root == NULL)
{
return;
}
if (root->_left == NULL&&root->_right == NULL)
{
//到叶子,生成编码
_infos[(unsigned char)root->_w._ch]._code = code;
return;
}
GetHuffmanCode(root->_left, code + '0');
GetHuffmanCode(root->_right, code + '1');
}
很多人会问什么是加0,而不用pushback,原因是因为如果我们pushback了,那么他就会把code传到下一层,
两种生成huffman编码的方法比起来,我们会选择,方法一,因为方法二的code消耗大,每次传给下一层&#