写在开头——这是一个耗时两周,几度中断,最终还是未完成的代码。
事情的源头是一次上机实验,这是一道选做题:
设计一个哈夫曼编码、译码系统。对一个 ASCII 编码的文本文件中的字符进行哈夫曼编码,生成编码 文件; 反过来,可将编码文件译码还原为一个文本文件。 (1) 从文件中读入任意一篇英文短文(文件为 ASCII 编码扩展名为 txt) (2) 统计并输出不同字符在文章中出现的频率(空格、换行、标点等也按字符处理); (3) 根据字符频率构造哈夫曼树,并给出每个字符的哈夫曼编码;
开始的时候我觉得这道题可以做啊,重点不就是哈夫曼树的建立吗,书上的代码白纸黑字。于是我就入坑了。。
从哪里入手呢?嗯,先写一个读写文件的函数吧! 突然我的脑子一片空白,对文件是怎么操作来着,我怎么毫无印象,才反应过来c语言的文件操作早已经忘得精光。
从柜底扒出来大一的红皮书,看了几眼,嗯,就这么回事,so easy。既然要挑战自己,那就不能守旧,同时我也很好奇c++对文件是怎么操作的。于是乎,我就怀着程序猿的使命就是创造世界的满腔热血来回谷歌,万千百度。
说起来惭愧,c++对文件的操作我是掌握了,却是用了一周的时间!期间我还发了我在csdn的第一个博客,就是那个c++对文件的操作。
对文件的读取用的方法取自前一篇博客。文件操作是解决了,可新问题又来了,统计字符。开始我就用两个数组data[ ],weight[ ] ,一个存字符,一个计数,不用想 死路一条。
于是,又专门创建一个类Data 来初步保存从文件读取的字符的种类以及权值。然后是把他们排个序,为下一步建立哈夫曼树做准备。
好了,现在原材料有了,可以开始写哈夫曼树了。可后来我哭了,我才明白算法思想了然于胸和用代码实现是天壤之别。翻开书,恭恭敬敬地看了好几遍代码,又加上我自己
的改造,算是基本完成。树已经建好了,接下来就是对各个字符编码了,没成想这一步是如此的艰难,再一次卡死。这是昨天下午的事了。
开始想用非递归实现加一个辅助栈,因为我一碰见递归就犯迷糊,可是二叉树就是为递归而生的,我只能老老实实的迷失在递归里。还好今天上午把他给解决了。
还剩最后两个问题没解决,把txt文件哈夫曼编码,把编码后的文件按照哈夫曼解码。写到这对于学渣的我已经是不容易了,以后继续努力吧。
也就是说我现在的代码可以把一个txt文件里的字符读取,建成一个哈夫曼树,并且给出各个字符对应的哈夫曼编码。
下面是长篇代码,写的比较简陋,也没能来得及优化。函数都写到类里面了。。。
#include<iostream>
#include<fstream>
#include<string.h>
#define MAX 80
using namespace std;
class Data
{
public:
char data;
int weight;
Data(char d=0,int w=0):data(d),weight(w){}
};
/*----------------哈夫曼树的节点类----------*/
class NodeHuff
{
public:
NodeHuff* left;
NodeHuff* right;
char code[MAX]; //code是哈夫曼编码后的01字符串
int weight;
char data;
NodeHuff(NodeHuff* l=NULL,NodeHuff* r=NULL,int w=0,char c=0):left(l),right(r),weight(w),data(c){}
~NodeHuff(){}
};
/*-----------------哈夫曼树类-----------------*/
class Huffmantree
{
private:
NodeHuff* root;
int number=0; //统计字符的种类数目
char data[MAX];
int weight[MAX];
public:
Huffmantree(NodeHuff* r=NULL){root=r;}
~Huffmantree()
{
deletetree(root);
}
NodeHuff* Getroot(){return root;}
void deletetree(NodeHuff* p) //删除以p为根的整棵树
{
if(p==NULL) return;
NodeHuff* l,*r;
l=p->left;
r=p->right;
deletetree(l);
deletetree(r);
delete l;
delete r;
}
/*--------------建立哈夫曼树---------------*/
void CreatHuffman()
{
NodeHuff* H[MAX];
for(int i=0;i<MAX;i++)
{
H[i]=new NodeHuff;
H[i]->data=data[i];
H[i]->weight=weight[i];
}
int j=0;
NodeHuff* p1,*p2,*p,*t;
for(int i=0;i<MAX;i++)
{
t=new NodeHuff;
p1=H[i];
p2=H[i+1];
t->left=p1;
t->right=p2;
t->weight=p1->weight+p2->weight;
p=t;
j=i+2;
while(j<MAX&&(p->weight>H[j]->weight))
{
H[j-1]=H[j];
j++;
}
H[j-1]=p;
}
root=H[number-1];
}
/*-------------------对文件中的字符计数并且按递增排序-------------------*/
void countAndSort()
{
Data cc[MAX]; //cc[]初步保存从文件读取的字符,后来再对其排序
char buffer[100];
ifstream infile("example.txt");
if(!infile.is_open())
{
cout<<"open error"<<endl;return;
}
char q;
while(infile.good()&&!infile.eof())
{
memset(buffer,0,100*sizeof(char));
infile.getline(buffer,100);
for(int i=0;i<100;i++)
{
q=buffer[i];
if(q==0) ;
else
{
int o;
for(o=0;o<MAX;o++)
{
if(cc[o].data==q)
{cc[o].weight++;break;}
}
if((o==MAX)&&(number!=MAX))
{
cc[number].data=q;cc[number].weight++;
number++;
}
}
}
}
for(int i=number;i<=MAX;i++)
{
cc[i].weight=999;
}
infile.close();
/*---------------对cc[]排序,将排好序的数据传给data[],weight[]--------------------*/
int j=0;
for(int k=0;k<number;k++)
{
for(int i=0;i<number;i++)
{
if(cc[j].weight>cc[i].weight)
j=i;
}
data[k]=cc[j].data;weight[k]=cc[j].weight;
cc[j].weight=999;
}
cout<<"---------各字符出现次数----------"<<endl<<endl;
for(int i=0;i<number;i++)
{
cout<<data[i]<<"\t"<<weight[i]<<"\t";
}
for(int i=number;i<MAX;i++)
{
data[i]=0;weight[i]=999;
}
}
/*---------按照哈夫曼树对字符编码------------*/
void Encode(NodeHuff* p,char *encode)
{
if(!p) return;
if(!(p->left)&&!(p->right))
{
for(int i=0;i<MAX;i++)
p->code[i]=encode[i];
cout<<p->data<<"\t"<<p->code<<endl;
return;}
NodeHuff*l,*r,*q;
char lcode[MAX];
char rcode[MAX];
l=p->left;int i=0;
while(encode[i]!='\0')
{
lcode[i]=encode[i];
i++;
};
lcode[i]='0';lcode[i+1]='\0';
r=p->right;i=0;
while(encode[i]!='\0')
{
rcode[i]=encode[i];
i++;
};
rcode[i]='1';rcode[i+1]='\0';
Encode(l,lcode);
Encode(r,rcode);
}
};
int main()
{
Huffmantree h;
h.countAndSort();
h.CreatHuffman();
char en[MAX]={'\0'};
cout<<"--------------------"<<endl<<endl;
h.Encode(h.Getroot(),en);
return 0;
}