项目名称
利用Huffman树实现文件压缩
项目概要
使用的编辑语言是C++,项目目的是能够实现对文件的压缩及解压,涉及到的技术主要有huffman树的实现,文件的IO操作,优先级队列等;
huffman算法原理: Huffman算法是一种无损压缩算法,Huffman需要得到每种字符出现概率,通过计算字符序列中每种字符出现的频率,为每种字符进行唯一的编码设计,使得频率高的字符占的位数短,而频率低的字符长,来达到压缩的目的。
压缩原理:首先是处理源文件,统计源文件的字符数,将源文件以IO流的方式打开,每次从流中读取一个字符,进行处理,并将其记录到已有的字符哈希表中,由于一个字符占一个字节(0 ~ 255),因此哈希表的空间设置为256即可。完成统计之后,用哈希表来构建huffman数,生成huffman编码,根据01编码将每个字符以一个到多个比特位的方式来写入压缩文件即可。
解压原理:在压缩文件中得到构建huffman树的所有数据(这里需要在压缩时将这些统计好的的数据同时写入压缩文件),根据这些数据将就可以将压缩时使用的huffman数还原,即可解析编码。之后读取压缩文件的所有编码,通过编码在huffman树上进行查找,得到原有的字符,并依次写入解压文件。
设计思路
整体思路
- 整个过程是依赖于huffman树,因此要构建出一个可供我们使用的Huffman树
- 压缩时,操作源文件,以字符形式读取文件信息,进行字符个数统计,将统计结果作为数据构建huffman树,生成huffman编码,进行压缩。
- 解压时,从压缩文件取出构建huffman树的数据,根据huffman树解析压缩文件。
具体实现
Huffman树的构建
- 将数据依次放入优先级队列中,构建成一个小堆。
- 每次从堆中拿出两个权值最小的数,进行操作,将操作后的权值再次放入堆中。
- 直到堆中的数据为空,就完成了huffman数的构造。
主要实现:
//构建Huffman树需要三个参数,分别为:统计之后的数据数组,数据个数,非空元素
HuffmanTree(T* data, size_t size, const T& end)
{
//使用优先级队列构建一个小堆 < 是大堆 > 是小堆
priority_queue<Node*, vector<Node*>, compare> heap;
//1.将所有数据构成结点,录入小堆中
for (size_t i = 0; i < size; ++i)
{
if (data[i] != end)
heap.push(new Node(data[i]));
}
//2. 开始构建HuffmanTree,每堆中取两个最小的数据,作为根节点的左子树和右子树,
// 并将根节点录入堆中,直到堆中没数据
Node* pN1;
Node* pN2;
Node* newNode=NULL;
while (heap.size() > 1)
{
pN1 = heap.top();
heap.pop();
pN2 = heap.top();
heap.pop();
newNode = new Node(pN1->_data + pN2->_data);
pN1->_parent = newNode;
pN2->_parent = newNode;
if (pN1->_data > pN2->_data)//出现频率高的放左边
{
newNode->_left = pN1;
newNode->_right = pN2;
}
else
{
newNode->_left = pN2;
newNode->_right = pN1;
}
heap.push(newNode);
}
_root = newNode;
}
压缩文件
文件压缩,首先统计源文件的所有字符,当完成字符统计之后,将数据传给huffman数的构造函数构造huffman树,之后便可以根据数进行生成huffman编码,将统计数据也要压入压缩文件,方便在进行解压之时,能够构造出同一棵huffman树。
在进行压缩时,从源文件读取一个字符,得到将字符的huffman编码,将编码以比特位的形式写入压缩文件,即够八位,进行一次写入。
主要实现:
//从压缩文件中读取一个字符,开始从HuffmanTree上开始查找,直到叶子结点,写入解压文件
int ch = 0;
int num = _root->_data._count;
Node* cur = _root;
while ( num > 0)
{
ch = fgetc(fp_r);
for (int i = 0; i < 8; i++)
{
if ((ch & (1 << i)) ) //为1
cur = cur->_left;
else
cur = cur->_right;
if (cur && cur->_left == NULL && cur->_right == NULL)//找到叶子结点
{
--num;
fputc((int)cur->_data._ch, fp_w);
cur = _root;
if (num == 0)
break;
}
}
}
解压文件
解压时,向从压缩文件中读取统计数据,即:
//1.先从压缩文件中将要构成HuffmanTree的数据(字符和字符总数)取出
struct Data { char _ch; size_t _count; }data;
FILE* fp_r = fopen(file, "rb");
fread(&data, sizeof(data), 1, fp_r);
int num = 0;
while (data._count != 0)
{
_hashtable[(unsigned char)data._ch]._count = data._count;
fread(&data, sizeof(data), 1, fp_r);
cout << num++ << endl;
}
之后,读取编码,开始从huffman树上解码,解码的过程是,从树的root开始,遇到1即走树的左子树,遇到0走树的右子树,直到走到叶子结点为止,将叶子结点上的字符,输入解压文件即可。
主要实现:
int ch = 0;
int num = _root->_data._count;
Node* cur = _root;
while ( num > 0)
{
ch = fgetc(fp_r);
for (int i = 0; i < 8; i++)
{
if ((ch & (1 << i)) ) //为1
cur = cur->_left;
else
cur = cur->_right;
if (cur && cur->_left == NULL && cur->_right == NULL)//找到叶子结点
{
--num;
fputc((int)cur->_data._ch, fp_w);
cur = _root;
if (num == 0)
break;
}
}
}
测试阶段:
经过多次的测试,虽然能够保证无损压缩,但是还是存在两个缺点:
- 压缩率不能保证,有些文件压缩之后比压缩之前的文件下不了多少。
- 速度慢,仅仅是4M左右的文件,也会耗费几秒,算法的处理还是不够。
如果大家有什么好的想法,可以想我提出。