1.任务:
[问题描述]
对一篇不少于5000字符的英文文章(source.txt),统计各字符出现的次数,实现Huffman编码(code.dat),以及对编码结果的解码(recode.txt)。
[基本要求]
(1) 输出每个字符出现的次数和编码,并存储文件(Huffman.txt)。
(2) 在Huffman编码后,英文文章编码结果保存到文件中(code.dat),编码结果必须是二进制形式,即0 1的信息用比特位表示,不能用字符’0’和’1’表示。
(3) 实现解码功能。
2.采用的数据结构
采用霍夫曼树和顺序查找
3.算法设计思想
从文件中对于文章进行逐字符读入,对于每一字符出现的总次数进行累加,得到各字母的权值。每一次在未标记的权值中寻找两个最小值,取和,将和加入数组中。将两个最小值进行标记,记录它们的根节点位置(即和所在的位置),记录和的左右子树所在的位置。直至只剩下一个未标记的元素为止,该元素即为霍夫曼树的根节点。根据数组中的位置记录可以得到霍夫曼树。时间复杂度T(n)=O(n^2), 空间复杂度S(n)=O(n).
利用霍夫曼树进行编码,规定霍夫曼树中左分支为0,右分支为1,从叶结点起,若其为其双亲结点的左节点则记0,为右节点则记1,结点上移,直至移至根节点。将所得的01序列逆序,则得到该叶结点的编码。
利用霍夫曼树进行解码,遇到0则向左子树下移,遇到1则向右子树下移,直至到达根节点。重复该过程实现01序列的解码。
4.源程序:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <fstream>
#include <string>
#include <algorithm>
using namespace std;
typedef struct Alpha
{
char alpha='\0';
int num=0;
bool flag=true;
string code="";
int Father=0,LChild=0,RChild=0;
}Alpha;
Alpha a[51];
typedef struct BiTNode
{
Alpha data;
BiTNode *LChild,*RChild;
}BiTNode,*BiTree;
void ReadFromFile(Alpha a[])
{
char ch;
int x;
FILE *fp;
if((fp=fopen("source.txt","r"))==NULL)
{
cout<<"文件打开失败!\n";
}
else
{
while(!feof(fp))
{
ch=fgetc(fp);
if(('a'<= ch) && (ch <='z'))
{
x=ch -'a';
a[x].num++;
continue;
}
else if('A'<= ch && ch <='Z')
{
x=ch-'A';
a[x].num++;
continue;
}
else
{
continue;
}
}
}
fclose(fp);
}
int SearchMin(Alpha a[],int n)
{
int min=5000,k;
for(int i=0;i<n;i++)
{
if(a[i].flag && (a[i].num<min))
{
min=a[i].num;
k=i;
}
}
return k;
}
char h[20];
void Encode(Alpha a[])
{
string s;
for(int i=0;i<26;i++)
{
s="";
int k=0,f=-1,c=i;
while(f!=0)
{
f=a[c].Father;
if(a[f].LChild==c)
{
s+='0';
}
if(a[f].RChild==c)
{
s+='1';
}
c=f;
}
reverse(s.begin(),s.end());
a[i].code=s;
}
}
void EncodeOutput(Alpha a[])
{
char ch;
int x;
ifstream infile;
infile.open("source.txt");
ofstream outfile;
outfile.open("code.txt");
if(outfile&&infile)
{
while(!infile.eof())
{
infile>>noskipws;
infile>>ch;
if('a'<=ch && ch<='z')
{
x=ch-'a';
outfile<<a[x].code;
}
else if('A'<=ch && ch<='Z')
{
x=ch-'A';
outfile<<a[x].code;
}
else if(ch==' ')
{
outfile<<" ";
}
else
{
outfile<<ch;
}
}
}
else
{
cout<<"打开失败!\n";
}
outfile.close();
infile.close();
}
void RecodeOutput(Alpha a[])
{
char ch;
int x;
ifstream infile;
infile.open("code.txt");
ofstream outfile;
outfile.open("recode.txt");
if(outfile&&infile)
{
infile>>noskipws;
while(!infile.eof())
{
infile>>ch;
if((ch!='0')&&(ch!='1'))
{
outfile<<ch;
}
else if(ch==' ')
{
cout<<" ";
}
else
{
int k=50;
while(k>25)
{
if(ch=='0')
{
k=a[k].LChild;
}
if(ch=='1')
{
k=a[k].RChild;
}
if(k>25)
{
infile>>ch;
}
}
outfile<<a[k].alpha;
}
}
}
else
{
cout<<"打开失败!\n";
}
outfile.close();
infile.close();
}
int main()
{
for(int i=0;i<26;i++)
{
a[i].alpha='a'+i;
}
ReadFromFile(a);
int n=26;
for(int i=0;i<25;i++)
{
int a1,a2;
a1=SearchMin(a,n);
a[a1].flag=false;
a2=SearchMin(a,n);
a[a2].flag=false;
a[n].num=a[a1].num+a[a2].num;
a[n].flag=true;
a[n].LChild=a1;
a[n].RChild=a2;
a[a1].Father=n;
a[a2].Father=n;
n++;
}
Encode(a);
EncodeOutput(a);
cout<<"文件编码成功!\n";
RecodeOutput(a);
cout<<"文件译码成功!\n";
return 0;
}
5.源程序测试数据及结果
霍夫曼树测试用例
霍夫曼树编码测试结果
霍夫曼树解码测试结果
6.存在问题及改进方法
在霍夫曼树的编码和解码中,为了代码编写的方便起见,我将大小写字母全部转化为了小写字母,这对于编码再解码后的内容可能会造成影响。其次,在编码过程中,只对字母进行了编码,忽视了各种标点符号,我将读入的非字母元素原封不动的输出到了输出文档中,这对于文件的编码的保密程度有了较大的影响,应该增加结点的个数,使各种符号等都加入编码过程才更加严谨。