2. 编程实现如下功能:
(1)建立由英文字符组成的文件f1(字符种类≥10,长度≥100),并统计不同字符出现的次数;
(2)按字符出现的次数对其建立哈夫曼树,并求出各个字符的哈夫曼编码;
(3)读入要编码的文件f1,编码后存入另一个文件f2;
(4)接着再调出编码后的文件f2,对其进行译码输出,最后存入文件f3。
#include<iostream>
#include<fstream>
#include<cstring>
using namespace std;
#define NODE 51 //节点数:26个字母,共需26*2-1=51个结点
#define ZI 26 //26个字母
typedef struct Huffman{ //Huffmantree结构
int weight;
int parent,lchild,rchild;
}Huffman,*HuffmanTree;
typedef char **HuffmanCode; //动态分配数组存储哈夫曼编码表
void Select(HuffmanTree T,int len,int &s1,int &s2){
int i,min1=100000,min2=100000; //初始化为第一个的值找出最小的权重值然后组合成新的树
for(i=1;i<=len;i++){ //这里字母的数量最小值都大于100000这个min就存在问题了(之后可以优化)
if(T[i].weight<min1 && T[i].parent==0){
min1=T[i].weight;
s1=i;
} //这里通过遍历找出第一个最小权重值
}
int temp=T[s1].weight;
T[s1].weight=100000; //这里为了找出第二个最小权重值,必须把第一个最小值临时存入temp,
//然后将它设为足够大才能保证不会重复使用,但其实和上面问题一样(可以优化)
for(i=1;i<=len;i++){
if (T[i].weight < min2 && T[i].parent == 0){
min2 = T[i].weight;
s2 = i; //找第二个最小值
}
}
T[s1].weight = temp; //恢复原来的值
}
void Create(HuffmanTree &T,int word[]){ //创建哈夫曼树,叶子结点26个,将文件中的字符统计数组计入树中
int i,j,s1,s2;
T=new Huffman[NODE+1];
for(i=1;i<=NODE;i++){
T[i].parent=0;
T[i].lchild=0;
T[i].rchild=0;
}
for(j=1;j<=26;j++)
T[j].weight=word[j];
/*-------------------- 以上皆为初始化 ---------------------*/
for(i=ZI+1;i<=NODE;i++){
Select(T,i-1,s1,s2); //找俩权重最小值(算法可优化)
T[s1].parent=i;
T[s2].parent=i;
T[i].lchild=s1;
T[i].rchild=s2;
T[i].weight=T[s1].weight+T[s2].weight;
}
}
void CreateHuffmancode(HuffmanTree T,HuffmanCode &HC){
int start;
HC=new char*[ZI+1]; //分配编码空间
char *cd=new char[ZI]; //因为哈夫曼编码不可能大于26
cd[ZI-1]='\0'; //编码结束符
for(int i=1;i<=ZI;i++){ //为每个字符编码
start=ZI-1; //这里start要在HC的最后面,也就是结束符的位置
int c=i;
int f=T[i].parent;
while(f!=0){
start--;
if(T[f].lchild==c)
cd[start]='0';
else
cd[start]='1';
c=f;
f=T[f].parent;
}
HC[i]=new char[ZI-start];
strcpy(HC[i],&cd[start]);
}
delete cd;
}
int main(){
int word[27]={0}; //建立数组存储26字母个数作为哈夫曼树权值
char c;
FILE *fp=fopen("f1.txt","r");
if(fp){
while((c=getc(fp))!=EOF){
int size=(int)c; //这里针对26字母的ASCII值将不同字母以1-26
if(size>=65 && size<=90) //大写化为小写
size=size+32;
if(size>=97 && size<=122){ //方便归一到数组
size=size-96;
word[size]++;
} //代入到word数组中方便后续统计
}
}
fclose(fp);
HuffmanCode HC;
HuffmanTree T;
Create(T,word); //创建哈夫曼树
CreateHuffmancode(T,HC); //哈夫曼编码
/* 下面这一段我调试了很久,无论怎么试都无法将字符打印到文件中,
后来修改发现,我如果重新创建一个FILE类型的文件对f1.txt重新
进行读取就可以打印出来,测试后发现是因为while((c=getc(fp3))!=EOF){
这里的代码为EOF,具体原因我猜想是getc函数具有记忆性,相当于它上次执行
以及读取到了最后面,再次读取时就会直接EOF */
/*------------------------- 编码 ----------------------------*/
FILE *fp3=fopen("f1.txt","r");
FILE *fp2=fopen("f2.txt","a");
if(fp3 && fp2){
while((c=getc(fp3))!=EOF){
int size=(int)c;
if(size>=65 && size<=90) //大小写转换
size=size+32;
if(size>=97 && size<=122){
size=size-96;
fputs(HC[size],fp2);
fputs("\n",fp2); //这里添加原因是因为vscode警告一行的数字太多
// 所以我就写入了一个换行符
}
if(size==32)
fputs("2",fp2); //这里是后续添加,因为发现译码时没有空格
//英文没有空格符,没有可识度,于是用2表示空格
}
}
fclose(fp2);
fclose(fp3); //这里防止又出现以上情况,这里直接close重新创建
/*------------------------ 译码 --------------------------*/
FILE *fp4=fopen("f2.txt","r");
FILE *fp5=fopen("f3.txt","a");
if(fp4 && fp5){
int h=NODE;
while((c=getc(fp4))!=EOF){
int ts=(int)c-48; //这里同样调试很久发现无法译码是因为getc返回的为char类型
if(ts==2) // 即便强制转换也会变为它对应的ASCII值,所以进行了转换
fputs("\n",fp5);
if(T[h].lchild==0 && T[h].rchild==0){
char ls=(char)(h+96);
fputc(ls,fp5);
h=NODE;
}
if(ts==1)
h=T[h].rchild;
if(ts==0)
h=T[h].lchild;
}
}
fclose(fp4);
fclose(fp5);
return 0;
}
f1.txt 随便找一段英文短句即可。
HAUT的宝贝们不要抄作业