首先,我们将文件压缩这个项目分为五个步骤:
- 1. 统计字符出现的次数
- 2. 构建HuffmanTree
- 3. 生成哈夫曼编码 (Huffman Code)
- 4. 压缩 (compress)
- 5. 解压缩 (uncompress)
Huffman树 ,又称为最优二叉树,是加权路径长度最短的二叉树。
【贪心算法】是指在问题求解时,总是做出当前看起来最好的选择。也就是说贪心算法做出的不是整体最优的的选择,而是某种意义上的局部最优解。贪心算法不是对所有的问题都能得到整体最优解。使用贪心算法构建Huffman树.
文件压缩的过程:
Input.txt
内容:aaaabbbcccd
1. 统计字母出现的次数:
a ------ 4
b ------ 3
c ------ 2
d ------ 1
2. 构建huffmanTree:
3. 生成huffman code(编码):
a -> 0 (* 出现次数多的字符(数值大)–路径短–编码短)
b -> 11
c -> 101
d -> 100
4. 压缩(按位存储):
文件 :Input.txt.huffman
内容 : 00001111 11101101 100(00000) ----按位存储
第三个字节后面的五个0是补位
(原文件占用10个字节,压缩后占用3个字节)
5. huffman解压缩:
还原文件:Input.txt.unhuffman
思考: 如果一个文件中内容比较多,有的字符原本只占一个字节,但是压缩后会占用好几个字节,这样压缩后会有效果吗?
答:因为出现次数最多的那个字符只占用1个或者2个位,而字节最多的那个只会出现1次后或者2次,相对于而言,会节省很大的空间。
整个项目中我们会遇到的问题:
1.压缩带中文的文件,程序就会崩溃。
最后发现数组越界的问题.
因为char它的范围是-128127,程序中使用char类型为数组下标(0127),所以字符没有问题,但是汉字是占两个字节的,所以会出现越界的问题,解决的方法就是char类型强转为unsigned char,它的范围为0~255.
2.解压缩文件生成后会丢失很多内容.
1. 文件的打开方式.
这里打开文件一定要用二进制形式,"wb","rb".因为二进制打开和文本打开其实是有区别的。
1.文本方式打开:会对‘\n’进行特殊处理,那如果这个字符本身就是'\n'.这就会出现问题。
2.二进制方式打开:不进行任何处理,是什么就是什么。
2. 文件结束符的问题。
刚开始用的文件结束标志是EOF在ASII中它的编码是-1,随后我们用了 二进制方式打开文件,而二进制文件是会出现-1的,所以提前结束了,所以我们用二进制读取并且不能用EOF来判断结束。
C中有一个函数叫做feof(),它可以检查流上文件的结束符,如果文件结束,则返回非0值,否则返回0
下面引用百科的解释:
feof(fp)有两个返回值:如果遇到文件结束,函数feof(fp)的值为非零值,否则为0。
EOF是文本文件结束的标志。在文本文件中,数据是以字符的ASCⅡ代码值的形式存放,普通字符的ASCⅡ代码的范围是32到127(十进制),EOF的16进制代码为0x1A(十进制为26),因此可以用EOF作为文件结束标志。[1]
当把数据以二进制形式存放到文件中时,就会有-1值的出现,因此不能采用EOF作为二进制文件的结束标志。为解决这一个问题,ASCI C提供一个feof函数,用来判断文件是否结束。feof函数既可用以判断二进制文件又可用以判断文本文件。
3.为什么要使用配置文件?
在项目中,将字符对应的编码转化为位,在unsigned char中填充位,填满后就写入到压缩文件中。
问题1:最后一个字节是不是很有可能没有填满,该如何判断他是否填满以及填了几个字符的编码?
问题2:若依次压缩一些文件,压缩完后再去解压,那么编码此时已经没有了,该如何解压?
上面的两个问题可通过配置文件解决,假如要压缩的文件叫xxx,那么可生成一个xxx.config的配置文件,在该配置文件中写入<文件的总长度>(恢复时知道应该恢复多少个字符),(字符以及其出现的次数,用于解压时重建哈夫曼树),利用该配置文件即可解决这两个问题。
完整代码实现:
包括:
Filecompress.h Heap.h HuffmanTree.h test.cpp
Filecompress.h:
#pragma once
#include<iostream>
#include<assert.h>
#include<algorithm>
#include<string>
#include"HuffmanTree.h"
struct CharInfo
{
unsigned char _ch; // 字符
long long _count; // 文件中出现的次数
string _code; // huffman code
CharInfo(unsigned char ch = 0)
:_ch(ch)
,_count(0)
{}
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;
}
};
class FileCompress
{
typedef HuffmanTreeNode<CharInfo> Node;
struct ConfigInfo
{
unsigned char _ch;
long long _count;
};
public:
//无构造函数的话就是随机值
FileCompress()
{
for (int i = 0; i < 256; ++i)
{
_infos[i]._ch = i;
_infos[i]._count = 0;
}
}
void GetHuffmantree(Node* root,string code)
{
if(root == NULL)
{
return ;
}
if(root->_left == NULL && root->_right==NULL)
{
_infos[(unsigned char)root->_w._ch]._code= code ;
return;
}
GetHuffmantree(root->_left,code+'0');
GetHuffmantree(root->_right,code+'1');
}
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)
{
if (cur == parent->_left)
{
code.push_back('0');
}
else if (cur == parent->_right )
{
code.push_back('1');
}
else
{
break;
}
cur = parent;
parent = cur->_parent;
}
reverse(code.begin(), code.end());
return;
}
GetHuffmanCode(root->_left);
GetHuffmanCode(root->_right);
}
// Input.txt->Input.txt.huffman 压缩
void Compress(const char* file)
{
assert(file);
//1,统计字符出现的次数
FILE* fout = fopen(file,"rb");
assert(fout);
char ch = fgetc(fout);
while(!feof(fout))
{
_infos[(unsigned char)ch]._count++; //这个字符的哪个位置的个数加一
ch = fgetc(fout);
}
//2,构建哈夫曼树
CharInfo invalid;
invalid._count = 0;
HuffmanTree<CharInfo> tree(_infos,256,invalid);
Node* root = tree.GetRoot ();
//3,生成哈夫曼编码
string code;
GetHuffmantree(root,code);
//4,压缩文件
// Input.txt->Input.txt.huffman
string compressfile = file;
compressfile += ".huffman";
FILE* fin = fopen(compressfile.c_str(),"wb");
for(size_t i=0;i<256;i++)
{
ConfigInfo info;
if(_infos[i]._count)
{
info._ch = _infos[i]._ch ;
info._count = _infos[i]._count ;
fwrite(&info,sizeof(ConfigInfo),1,fin);
}
}
ConfigInfo info;
info._count = 0;
fwrite(&info,sizeof(ConfigInfo),1,fin);
fseek(fout,0,SEEK_SET);
ch = fgetc(fout);
char value = 0;
char pos = 0;
while(!feof(fout))
{
string& code = _infos[(unsigned char)ch]._code;
for(size_t i = 0;i<code.size();++i)
{
//将编码变成二进制
if(code[i] == '1')
{
value |= (1<<pos);
}
else if(code[i] == '0')
{
value &= (~(1<<pos));
}
else
{
assert(false);
}
++pos;
if(pos == 8)
{
fputc(value,fin);
value = 0;
pos = 0;
}
}
ch = fgetc(fout);
}
if(pos)//补位
{
//value <<= (8-pos);
fputc(value,fin);
}
fclose(fout);
fclose(fin);
}
//Input.txt.huffman -> Input.txt.unhuffman
void Uncompress(const char* file)
{
assert(file);
string uncompressfile = file;
size_t pos = uncompressfile.rfind('.');
assert(pos != string::npos);
uncompressfile.erase(pos,uncompressfile.size()-pos);
uncompressfile += ".unhuffman";
FILE* fin = fopen(uncompressfile.c_str(),"wb");
assert(fin);
FILE* fout = fopen(file,"rb");
assert(fout);
//读配置信息
while(!feof(fout))
{
ConfigInfo info;
info._count = 0;
fread(&info,sizeof(ConfigInfo),1,fout);
if(info._count )
{
_infos[info._ch ]._count = info._count ;
}
else
{
break;
}
}
CharInfo invalid;
invalid._count = 0;
HuffmanTree<CharInfo> tree(_infos,256,invalid);
Node* root = tree.GetRoot();
Node* cur = root;
long long charcount = 0;
charcount = tree.GetRoot ()->_w._count;
pos = 0;
char value = fgetc(fout);
while(!feof(fout))
{
if(value & (1<<pos))
cur = cur->_right;
else
cur = cur->_left;
if(cur->_left == NULL && cur->_right == NULL)
{
fputc(cur->_w._ch,fin);
cur = root;
if(--charcount == 0)
{
break;
}
}
++pos;
if(pos == 8)
{
value = fgetc(fout);
pos = 0;
}
}
fclose(fout);
fclose(fin);
}
protected:
CharInfo _infos[256];
};
Heap.h:
#pragma once
#include<iostream>
#include<assert.h>
#include<vector>
using namespace std;
template<class T>
struct Less
{
bool operator()(const T& left, const T& right) const
{
return left < right;
}
};
template<class T>
struct Greater
{
bool operator()(const T& left, const T& right) const
{
return left > right;
}
};
template<class T, class Compare=Less<T>>
class Heap
{
public:
Heap()//无参的构造函数(系统不会给无参构造函数),开始堆是空的不需要做什么事
{}
Heap(T* a, size_t n)
{
_a.reserve(n);//开空间
for (size_t i = 0; i < n; ++i)
{
_a.push_back(a[i]);
}
//建堆,找最后一个非叶子节点
for (int i = (_a.size() - 2) / 2; i >= 0; --i)//不用size_t,因为i在这可能等于0,用size_t会死循环
{
AdjustDown(i);
}
}
//向下调整
void AdjustDown(int root)
{
Compare com;
int parent = root;
size_t child = parent * 2 + 1;//默认为左孩子
while (child < _a.size())
{
//选出小孩子
//if (child+1 > _a.size() && _a[child + 1]< _a[child])
if (child + 1 < _a.size() && com(_a[child + 1], _a[child]))
{
++child;
}
//if (_a[child] < _a[parent])
if (com(_a[child], _a[parent]))
{
swap(_a[child], _a[parent]);//交换值
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//向上调整
void AdjustUp(int child)
{
Compare com;
int parent = (child - 1) / 2;
while (parent >= 0)
{
//if (_a[child] < _a[parent])
if (com(_a[child], _a[parent]))
{
swap(_a[parent], _a[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//最后插入
void Push(const T&x)
{
_a.push_back(x);
AdjustUp(_a.size() - 1);
}
//删除最大数
void Pop()
{
assert(!_a.empty());
swap(_a[0], _a[_a.size() - 1]);
_a.pop_back();
AdjustDown(0);
}
//取顶元素
T& Top()
{
assert(!_a.empty());
return _a[0];
}
size_t Size()
{
return _a.size();
}
bool Empty()
{
return _a.empty();
}
private:
vector<T> _a;
};
HuffmanTree.h:
#pragma once
#include<iostream>
#include<assert.h>
#include"heap.h"
using namespace std;
template<class W>
struct HuffmanTreeNode
{
HuffmanTreeNode<W>* _left;
HuffmanTreeNode<W>* _right;
HuffmanTreeNode<W>* _parent;
W _w;
HuffmanTreeNode(const W& w)
:_w(w)
, _parent(NULL)
, _left(NULL)
, _right(NULL)
{}
};
template <class W>
class HuffmanTree
{
typedef HuffmanTreeNode<W> Node;
public:
HuffmanTree()
:_root(NULL)
{}
HuffmanTree(W* a, size_t n , const W& invalid)
{
assert(a);
struct NodeCompare
{
bool operator()(Node *l, Node *r) //仿函数
{
return l->_w < r->_w;
}
};
Heap<Node*, NodeCompare> minHeap;//构造结点但是比较的是权值
for (size_t i = 0; i < n; ++i)
{
if (a[i] != invalid)
{
minHeap.Push(new Node(a[i]));
}
}
while (minHeap.Size()>1)
{
Node* left = minHeap.Top();
minHeap.Pop();
Node* right = minHeap.Top();
minHeap.Pop();
Node* parent = new Node(left->_w + right->_w);
parent->_left = left;
parent->_right = right;
left->_parent = parent;
right->_parent = parent;
minHeap.Push(parent);
}
_root = minHeap.Top();
}
size_t Size()
{
return minHeap.size();
}
Node* GetRoot()
{
return _root;
}
private:
Node* _root;
};
test.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include "FileCompress.h"
using namespace std;
void testHuffmanTree()
{
int a[]={0,1,2,3,4,5,6,7,8,9};
int size=sizeof(a)/sizeof(a[0]);
HuffmanTree<int> ht(a,size,0);
}
void testFileCompress()
{
FileCompress fc;
fc.Compress("input.txt");
fc.Uncompress("input.txt.huffman");
}
int main()
{
testHuffmanTree() ;
testFileCompress();
return 0;
}