说到HUFFMAN编码可以用来进行压缩,然后就想试试。拖了一周才开始写。纠结了两天。现在有点样子了,但是。。有BUG。。。有的时候可以有的时候不可以。。。。正在找原因。对文件操作和计算机原理这一块还不是很熟。
理一下思路吧。
先从一个文件中读取数据,进行频数统计,然后根据统计的频数建立HUFFMAN树。再扫描一遍文件,利用HUFFMAN树进行编码。此处有技巧!因为要进行压缩,所以对于每个字符的编码应用位来储存。这时候就不能想着用字符串来表达位啦!取一个字符c。我们知道一个字符有8位(一个字节),然后把c当做一个字节来看,而不是一个字符。对它进行位的存储。集满8位编码就把它存到输出文件中。
结束后从输出文件中读入字符,进行解码,把结果放到另一个文件中,和读入文件进行比对。
这里有偷懒,编码的时候应该把HUSH情况(字符对应的编码)一起放进去。这样如果程序只是执行解码操作时也是可以执行的。
建立HUFFMAN树可以用优先队列进行,这样比较方便,效率也高一些。但是要注意,如果没有对优先队列的算子?进行重定义,若放的是节点指针是不可取的!!这里再次验证了指针真的只是一个int!此处有坑。
然后是unsigned char 和 char。之前只知道有unsigned int 和 int,并且从来认为unsigned int是一个多余的类型。也从来认为补码只在题目中出现。这次俩碰一块了。因为只有8位,如果直接定义char,那么范围是-128~127,可是字符有256个,所以要用unsigned char来接收,这样就没问题啦。计算机存数据的时候都是用补码来存放的,并不区分类型,只有当你使用的时候类型才有了意义。
HUFFMAN编码是无所压缩最优的办法,但是当文件过小就可能出现压缩不利的情况,或者文件过大使得每个字符的出现频率相近,压缩率也很小。
看看我千疮百孔的代码。仍需修改但是在头疼所以mark一下以兹鼓励。其中有些是用来debug的。。。
***********************************************************************
终于知道哪里错了!!!!!就说哪里好奇怪的呢。。
这么大的一个bug为什么我会忽视掉??
在编码的时候,如果编到最后一位,未满一个字节是往后补零的。但是!这很有可能正好碰到一个huffman编码是这若干个零的!所以!在读入文件的时候要计算字符的个数,在解码的时候如果到达这个个数了,就要跳出循环!
我个傻叉QAQ。然后在读入文件的时候我省事了,没有输入路径(一不小心手抖了就错了这样测试起来很麻烦的说)。
顺便让我把代码改的优雅一点。
#include <stdio.h>
#include <string.h>
#include <queue>
#include <string>
#include <iostream>
using namespace std;
class node
{
public:
int l,r,sum,pos;
char c;
string wod;
node(){}
node(int l,int r,int sum,int pos,char c,string wod):l(l),r(r),sum(sum),c(c),wod(wod),pos(pos){}
void init(int l,int r,int sum,int pos,char c,string wod)
{this->l=l,this->r=r,this->sum=sum,this->c=c,this->wod=wod;this->pos=pos;}
friend bool operator < (const node a,const node b){return a.sum>b.sum;}
}tree[512];
int hashd[256],rehash[256];
void build(int p,string s)
{
if(p==-1) return;
if(tree[p].l==-1&&tree[p].r==-1)
{
tree[p].wod=s;
return;
}
build(tree[p].l,s+"0");
build(tree[p].r,s+"1");
}
int search(int root,int p)
{
if(p) return tree[root].r;
else return tree[root].l;
}
int move(int a,int b)
{
while(b--) a<<=1;
return a;
}
char s[8];
char* change(int x)
{
char tmp[8];
for(int i=0;i<8;i++) {tmp[i]=x&1;x>>=1;}
for(int i=0;i<8;i++) s[i]=tmp[7-i];
return s;
}
int main()
{
//文件预操作
FILE *ifp,*ofp;
char infile[257],outfile[257];
cout<<"输入文件的路径:"<<endl;
cin>>infile;
ifp=fopen(infile,"rb");
if(ifp==NULL) {cout<<"Fail to open"<<endl;return 0;}
cout<<"输出文件的路径:"<<endl;
cin>>outfile;
ofp=fopen(outfile,"wb");
if(ofp==NULL) {cout<<"Fail to write"<<endl;return 0;}
int c;int len=0;
memset(hashd,0,sizeof(hashd));
//建立huffman树,获得编码
while((c=fgetc(ifp))!=EOF)
{
hashd[c]++;
len++;
}
create huffman
int top=0;
priority_queue<node>que;
for(int i=0;i<256;i++)
{
if(!hashd[i]) continue;
tree[top].init(-1,-1,hashd[i],top,i,"");
rehash[i]=top;
que.push(tree[top++]);
}
while(que.size()>1)
{
node p=que.top();que.pop();
node h=que.top();que.pop();
tree[top].init(p.pos,h.pos,p.sum+h.sum,top,-1,"");
que.push(tree[top++]);
}
node root=que.top();que.pop();
build(root.pos,"");
fseek(ifp,0,SEEK_SET);
char to=0;
int size=0;
int j=0;
//进行编码
while((c=fgetc(ifp))!=EOF)
{
int num=rehash[c];
string tmp=tree[num].wod;
int i=0;
while(i<tmp.length())
{
for(j=size;j<8&&i<tmp.length();j++,i++)
{
to<<=1;
if(tmp[i]=='1') to|=1;
}
size=j%8;
if(size==0) {fwrite(&to,1,1,ofp);to=0;}
}
}
if(size!=0)
{
for(j=size;j<8;j++) to<<=1;
fwrite(&to,1,1,ofp);
}
fclose(ifp);
fclose(ofp);
//译码
ifp=fopen(infile,"wb");
ofp=fopen(outfile,"rb");
int st=root.pos;
string buf="";
while((c=fgetc(ofp))!=EOF)
{
buf+=char(c);
}
int lent=0;
for(int i=0;i<buf.length();i++)
{
for(int j=0;j<8;j++)
{
int k=move(1,7-j);
st=search(st,buf[i]&k);
if(tree[st].l==-1&&tree[st].r==-1)
{fwrite(&tree[st].c,1,1,ifp);lent++;st=root.pos;}
if(lent>=len) break;
}
if(lent>=len) break;
}
fclose(ifp);
fclose(ofp);
return 0;
}