Huffman的应用之文件压缩与解压缩
最近这段时间一直在学习树的这种数据结构,也接触到了Huffman树以及了解了什仫是Huffman编码,而我们常用的zip压缩也是利用的Huffman编码的特性,那仫是不是可以自己实现一个文件压缩呢?当然可以了.在文件压缩中我实现了Huffman树和建堆Heap的代码,zip压缩的介绍>
下面开始介绍自己实现的文件压缩的思路和问题...
1).统计>读取一个文件统计这个文件中字符出现的次数.
2).建树>以字符出现的次数作为权值使用贪心算法构建Huffman树(根据Huffman树的特性>字符出现次数多的一定靠近根结点,出现次数少的一定远离根结点).
3).生成Huffman编码>规则左0右1.
4).压缩>再次读取文件,根据生成的Huffman编码压缩文件.
5).生成配置文件>将字符以及字符出现的次数写进配置文件中.
6).解压缩>利用配置文件还原出Huffman树,根据压缩文件还原出原文件.
7).测试>判断解压是否正确需要判断原文件和解压缩之后的文件是否相同,利用BeyondCompare软件进行对比.
下面是我举的一个简单的范例,模拟压缩和解压缩的过程,希望有读者有帮助
利用BeyondCompare软件进行对比>
在实现中出现了很多的问题,下面我提出几个容易犯的问题,仅供参考
1).在使用贪心算法构建Huffman树的时候,如果我们以unsignedchar一个字节来存储它总共有2^8=256个字符,如果将所有的字符都构建Huffman树,这不仅降低了效率还将分配极大的内存.所以我设立了非法值这个概念,只有当字符出现的次数不为0的时候才将该字符构建到Huffman树中.
2).在写压缩文件的时候我们是将字符对应的Huffman编码转化为对应的位,每当填满一个字节(8位)后再写入压缩文件中.如果最后一个字节没有填满我们就将已经填的位移位空出后几个位置,将未满的位置补0补满一个字节再写入压缩文件中.
3).如果我们将源文件压缩之后再去解压,因为你的Huffman树和Huffman编码都是在压缩函数中得到的,此时再去解压那仫你的Huffman编码以及不存在了该如何去还原文件呢?这就是为什仫要生成配置文件的原因了,在配置文件中写入了字符以及字符出现的次数.在解压缩中根据配置文件构建新的Huffman树.
4).由压缩文件还原文件的时候如何知道压了多少个字符呢?也就是说因为我们在压缩的时候最后一个字节是补了0的在解压缩的时候可能会把这个补的位当成字符的编码来处理.一种想法是在统计字符出现的次数的时候设置一个变量,每读取一个字符该变量就加1,最后将该变量写进配置文件中.另外一种想法就是根据根结点的权值,通过上述简单的实例观察可知根结点权值中的_count就是字符出现的次数.
解决了以上几个问题,我的程序已经可以压缩那256个字符并正确的还原了,那仫如果是大文件或者是汉字,图片以及音频视频呢?
1).因为有些特殊的字符编码,所以我们统计字符出现的次数的时候应该用的是unsignedchar,刚开始我用的文件结束标志是EOF在ASII中它的编码是-1此时已经不可以用EOF来判断是否文件结束了,所以我用了feof这个函数来判断文件是否结束.
2).统计字符出现次数应该用的类型是longlong,这就解决了大文件的压缩和解压缩的问题了.
3).因为汉字,图片,视频这些在内存中是以二进制的形式存在的,所以我们将以文本形式打开读或者写的修改为以二进制的形式读或者写.
为了验证大文件的压缩我找了一个8.09M的文件压缩之后是6.50M,并且可以正确还原.
1).测试效率>
2).利用BeyondCompare软件进行对比,如果一样说明压缩成功>
[cpp]viewplaincopyprint?在CODE上查看代码片派生到我的代码片
#define_CRT_SECURE_NO_WARNINGS1
#pragmaonce
#include"Heap.h"
template
structHuffmanTreeNode
{
T_weight;
HuffmanTreeNode_left;
HuffmanTreeNode_right;
HuffmanTreeNode_parent;
HuffmanTreeNode(constT&w=T())
:_weight(w)
,_left(NULL)
,_right(NULL)
,_parent(NULL)
{}
};
template
classHuffmanTree
{
typedefHuffmanTreeNodeNode;
public:
HuffmanTree()
:_root(NULL)
{}
HuffmanTree(constTa,size_tsize)
:_root(NULL)
{
_root=_CreatHuffmanTree(a,size);
}
//将未出现的字符过滤出来,不构造堆
HuffmanTree(constTa,size_tsize,constT&invalid)
{
_root=_CreatHuffmanTree(a,size,invalid);
}
NodeGetRoot()
{
return_root;
}
~HuffmanTree()
{
_Destroy(_root);
}
protected:
Node_CreatHuffmanTree(constTa,size_tsize)
{
structNodeLess
{
booloperator()(Nodel,Noder)const
{
returnl->_weight_weight;
}
};
HeapminHeap;
//建立结点并放入vector中
for(size_ti=0;i {
Nodetmp=newNode(a[i]);
minHeap.Push(tmp);
}
//取出较小的两个结点作为左右孩子并构建父结点
while(minHeap.Size()>1)
{
Nodeleft=minHeap.Top();
minHeap.Pop();
Noderight=minHeap.Top();
minHeap.Pop();
Nodeparent=newNode(left->_weight+right->_weight);
parent->_left=left;
parent->_right=right;
left->_parent=parent;
right->_parent=parent;
minHeap.Push(parent);
}
returnminHeap.Top();
}
//思路类似不带过滤功能的
Node_CreatHuffmanTree(constTa,size_tsize,constT&invalid)
{
structNodeLess
{
booloperator()(Nodel,Noder)const
{
returnl->_weight_weight;
}
};
HeapminHeap;
//建立结点并放入vector中
for(size_ti=0;i {
if(a[i]!=invalid)
{
Nodetmp=newNode(a[i]);
minHeap.Push(tmp);
}
}
//取出较小的两个结点作为左右孩子并构建父结点
while(minHeap.Size()>1)
{
Nodeleft=minHeap.Top();
minHeap.Pop();
Noderight=minHeap.Top();
minHeap.Pop();
Nodeparent=newNode(left->_weight+right->_weight);
parent->_left=left;
parent->_right=right;
left->_parent=parent;
right->_parent=parent;
minHeap.Push(parent);
}
returnminHeap.Top();
}
void_Destroy(Node&root)
{
if(root==NULL)
return;
Nodecur=root;
if(cur)
{
_Destroy(cur->_left);
_Destroy(cur->_right);
deletecur;
cur=NULL;
return;
}
}
protected:
Node_root;
};
voidtestHuffmanTree()
{
inta[]={0,1,2,3,4,5,6,7,8,9};
intsize=sizeof(a)/sizeof(a[0]);
HuffmanTreeht(a,size);
}
[cpp]viewplaincopyprint?在CODE上查看代码片派生到我的代码片
#define_CRT_SECURE_NO_WARNINGS1
#pragmaonce
//利用仿函数的特性实现代码的复用性
template
structSmall
{
booloperator()(constT&l,constT&r)
{
returnl }
};
template
structLarge
{
booloperator()(constT&l,constT&r)
{
returnl>r;
}
};
template>//缺省是建小堆
classHeap
{
public:
Heap()
{}
Heap(constTa,intsize)
{
assert(a);
_a.reserve(size);
for(inti=0;i {
_a.push_back(a[i]);
}
//建堆的时候从倒数第一个非叶子结点开始.
for(intj=(size-2)/2;j>=0;--j)
{
_AdjustDown(j);
}
}
voidPush(constT&x)
{
_a.push_back(x);
_AdjustUp(_a.size()-1);
}
voidPop()
{
assert(!_a.empty());
swap(_a[0],_a[_a.size()-1]);
_a.pop_back();
_AdjustDown(0);
}
size_tSize()
{
return_a.size();
}
boolEmpty()
{
return_a.empty();
}
constT&Top()const
{
assert(!_a.empty());
return_a[0];
}
voidDisplay()
{
for(size_ti=0;i<_a.size();++i)
{
cout<<_a[i]<<"";
}
cout< }
protected:
void_AdjustDown(introot)
{
intparent=root;
size_tchild=2root+1;
while(child<_a.size())
{
Comparecom;
//child指向左右孩子中较大的那个数
//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默认指向左孩子
child=2parent+1;
}
else
break;
}
}
void_AdjustUp(intchild)
{
while(child>0)
{
intparent=(child-1)/2;
Comparecom;
//if(_a[child]>_a[parent])
if(com(_a[child],_a[parent]))
{
swap(_a[child],_a[parent]);
child=parent;
}
else
//插入的数据比父节点的数据域小
break;
}
}
protected:
vector_a;
};
//利用堆解决优先级队列的问题
template>
classPriorityQueue
{
public:
PriorityQueue(inta,intsize)
:_pq(a,size)
{}
voidPush(constT&x)
{
_pq.Push(x);
}
voidPop()
{
_pq.Pop();
}
constT&Top()const
{
return_pq.Top();
}
voidDisplay()
{
_pq.Display();
}
protected:
Heap_pq;
};
[cpp]viewplaincopyprint?在CODE上查看代码片派生到我的代码片
#define_CRT_SECURE_NO_WARNINGS1
#pragmaonce
#include"HuffmanTree.h"
typedeflonglongType;
structCharInfo
{
unsignedchar_ch;//出现的字符
Type_count;//统计次数
string_code;//Huffman编码
CharInfo(Typecount=0)
:_ch(0)
,_count(count)
,_code("")
{}
//重载对应的操作符
CharInfooperator+(constCharInfo&fc)const
{
returnCharInfo(_count+fc._count);
}
booloperator!=(constCharInfofc)const
{
return_count!=fc._count;
}
booloperator<(constCharInfo&fc)const
{
return_count }
};
classFileCompress
{
public:
//默认的构造函数
FileCompress()
{
for(size_ti=0;i<256;++i)
{
_infos[i]._ch=i;
}
}
stringCompress(constcharfilename)
{
assert(filename);
FILEpf=fopen(filename,"rb");
assert(pf);
unsignedcharch=fgetc(pf);
//统计字符出现的次数
while(!feof(pf))
{
_infos[ch]._count++;
ch=fgetc(pf);
}
//以该字符出现的次数构建一颗HuffmanTree.
CharInfoinvalid;//非法值
HuffmanTreeht(_infos,256,invalid);
//生成Huffman编码
stringcode;
_CreatHuffmanCode(ht.GetRoot(),code);
//_CreatHuffmanCode(ht.GetRoot());
//压缩文件
fseek(pf,0,SEEK_SET);//回到文件头
stringcompressfile=filename;
compressfile+=".compress";//压缩后的文件名
FILEfin=fopen(compressfile.c_str(),"wb");
assert(fin);
size_tpos=0;//记录位数
unsignedcharvalue=0;
ch=fgetc(pf);
while(!feof(pf))
{
string&code=_infos[ch]._code;
for(size_ti=0;i {
value<<=1;
if(code[i]==''1'')
value|=1;
else
value|=0;//do-nothing
++pos;
if(pos==8)//满一个字节
{
fputc(value,fin);
value=0;
pos=0;
}
}
ch=fgetc(pf);
}
if(pos)//解决不足8位的情况.
{
value<<=(8-pos);
fputc(value,fin);
}
//配置文件--便于重建Huffman树
stringconfigfilwww.shanxiwang.netename=filename;
configfilename+=".config";
FILEfinconfig=fopen(configfilename.c_str(),"wb");
assert(finconfig);
stringline;
charbuff[128];
for(size_ti=0;i<256;++i)
{
//一行一行的读
if(_infos[i]._count)
{
line+=_infos[i]._ch;
line+=",";
line+=_itoa(_infos[i]._count,buff,10);
line+="\n";
//fputs(line.c_str(),finconfig);
fwrite(line.c_str(),1,line.size(),finconfig);
line.clear();
}
}
fclose(pf);
fclose(fin);
fclose(finconfig);
returncompressfile;
}
stringUnCompress(constcharfilename)
{
assert(filename);
stringconfigfilename=filename;
size_tindex=configfilename.rfind(".");
configfilename=configfilename.substr(0,index);
configfilename+=".config";
FILEfoutconfig=fopen(configfilename.c_str(),"rb");
assert(foutconfig);
stringline;
//读取配置文件--获取字符出现的次数
unsignedcharch=0;
while(ReadLine(foutconfig,line))
{
if(line.empty())
{
line+=''\n'';
continue;
}
//读到空行
ch=line[0];
_infos[ch]._count=atoi(line.substr(2).c_str());
line.clear();
}
//构建Huffman树
CharInfoinvalid;
HuffmanTreehft(_infos,256,invalid);
//根结点的权值也就是字符出现的次数总和
HuffmanTreeNoderoot=hft.GetRoot();
Typecharcount=root->_weight._count;
//解压缩
stringuncompressfilename=filename;
index=uncompressfilename.rfind(".");
uncompressfilename=uncompressfilename.substr(0,index);
uncompressfilename+=".uncompress";
FILEfin=fopen(uncompressfilename.c_str(),"wb");
assert(fin);
//由压缩文件还原文件
stringcompressfilename=filename;
FILEfout=fopen(compressfilename.c_str(),"rb");
assert(fout);
HuffmanTreeNodecur=root;
intpos=7;
ch=fgetc(fout);
while(charcount>0)
{
while(cur)
{
if(cur->_left==NULL&&cur->_right==NULL)
{
//叶子结点
fputc(cur->_weight._ch,fin);
cur=root;
--charcount;
if(charcount==0)//所有的字符都处理完成
break;
}
if(ch&(1< cur=cur->_right;//1向右走
else
cur=cur->_left;//0向左走
--pos;
if(pos<0)//一个字节解压完成
{
ch=fgetc(fout);
pos=7;
}
}
}
fclose(foutconfig);
fclose(fin);
fclose(fout);
returnuncompressfilename;
}
//读取一行字符并放在line中
boolReadLine(FILEfout,string&line)
{
intch=fgetc(fout);
if(ch==EOF)
returnfalse;
while(ch!=EOF&&ch!=''\n'')
{
line+=ch;
ch=fgetc(fout);
}
returntrue;
}
protected:
//递归的方法求HuffmanTreeCode
void_CreatHuffmanCode(HuffmanTreeNoderoot,string&code)
{
if(root==NULL)
return;
_CreatHuffmanCode(root->_left,code+''0'');
_CreatHuffmanCode(root->_right,code+''1'');
if(root->_left==NULL&&root->_right==NULL)//叶子结点
{
_infos[root->_weight._ch]._code=code;
}
}
//非递归求HuffmanTreeCode
void_CreatHuffmanCode(HuffmanTreeNoderoot)
{
if(root==NULL)
return;
_CreatHuffmanCode(root->_left);
_CreatHuffmanCode(root->_right);
if(root->_left==NULL&&root->_right==NULL)//叶子结点
{
string&code=_infos[root->_weight._ch]._code;
HuffmanTreeNodecur=root;
HuffmanTreeNodeparent=root->_parent;
while(parent)
{
if(parent->_left==cur)
code.push_back(''0'');//左0
else
code.push_back(''1'');//右1
cur=parent;
parent=cur->_parent;
}
//编码是从根到叶子结点的,需要逆置
reverse(code.begin(),code.end());
}
}
protected:
CharInfo_infos[256];
};
voidtestFileCompress()
{
FileCompressfc;
cout<<"开始压缩"< cout<<"压缩用时:";
intstart=GetTickCount();
fc.Compress("2.png");//inputinput.BIG3.mp3
intend=GetTickCount();
cout< cout<<"开始解压"< cout<<"解缩用时:";
start=GetTickCount();
fc.UnCompress("2.png.compress");//input.compressinput.BIG.compress3.mp3
end=GetTickCount();
cout< }
voidtestFileUncompress()
{
FileCompressfc;
cout<<"开始解压"< cout<<"解缩用时:";
intstart=GetTickCount();
fc.UnCompress("2.png");
intend=GetTickCount();
cout< }
经过测试这个小项目已经可以压缩并还原一些文件了,目前还没有发现什仫大的Bug,如果有童鞋发现请第一时间告诉我哦...
|
|