1. (简答题)
给定一个文本文档(需含有中文,英文,数字,其它字符等,至少1000个字符以上)source.txt 。
1.计算出文档中每个字符出现的频率,对于每个字符生成对应的哈夫曼编码,结果保存在code.txt中。
2.再将源文source.txt中的每个字符用哈夫曼编码替换生成密文encrypt.txt文档(注:不要含有多余的空格)。
3.再把密文encrypt.txt根据code.txt的编码规则还原成明文unencrypt.txt文档。
这里给大家看看我要加密的source.txt
这个是第一代版本,不可以有空格
sinjituwaitumohitotuThisarticlewaswrittenbythemessyprogrammingape数字:6320.3587412FTYUIOKL,MNBGFtyuio4f54564d5sf324324werewrewrewreewrweoioiuosdbdfbs543454*/-*/-*/*-/*/*/**/*/*/*/*//fsd4f54564d5sf324324werewrewrewreewrweoioiuosdbdfbs543454*/-*/-*/*-/*/*/**/*/*/*/*//fsd【lklsa65d4as6das56c./,.,lasjdkafjkahfjkahsjkdhasjhjkchjkhxjkhjj852389578346572732gdf[;[];[,';m.,;kjk1l5jk1,5hj46k54g5hnfdgoijsifocals,dl;a,codfivsdnjkfndsjfuagfasdasklnscafsas23840923846515410-=0-=0-=0--0-=0-=043256465456456ds4f54564ssd5sf324324werewrewrewreewrweoioiuosdbdfbs543454*/-*/-*/*-/*/*/**/*/*/*/*//fsdsssssssssssssssssssssss---**werewr/werwefwerwer*w/-wer/e*w-*erww*e-r/e*w-/r*we/fdsfsdfdsfdsfdsfdsfwerroklcmvklcxkljosdjvkncvknxdaskdhasjkdhaskjdhasjkdhjaskhdjkashdjksahjkdhasjkdhasjkdhjaskhasjdhaskdhashkjashjckbaskjacjkashkjashdjaskdhsajkdhsajkdbsahcbcwugfygcbewhcwyechwecyuewvcgewvcgevcgewvcgewvecjhwecywecyewbasdasssdhjbchgwuidhwcjbcbcuiwhcbwchwuibcw6320.3587412FTYUIOKL,MNBGFtyuio7414589632586JHGFGHJKL85258/*-+6320.3587412FTYUIOKL,MNBGFtyuio7414589632586JHGFGHJKL85258/*4f54564d5sf324324werewrewrewreewrweoioiuosdbdfbs543454*/-*/-*/*-/*/*/**/*/*/*/*//fsd-+日语:真実はいつも一つ罗马音阿拉伯语:هالستاروي中文:这是一个测试文档韩语진실은하나야啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
下面上代码:
//哈夫曼编码(选做题)
#include<bits/stdc++.h>
#include<windows.h>
using namespace std;
#define MAX 100000
#define MMAXX 0x3fffffff
typedef struct//定义叶子的属性
{
char data;// 结点值
double wt;//权重(其实就是路径长度)
int parents;//祖先
int lchild;//左右孩子
int rchild;
}HT;
typedef struct
{
char cd[MAX];//存放哈夫曼编码
int start;//所以start用于指向最开始的节点
}HC;
//两个STL
map<char, bool>check;//统计是否出现过
map<char, int>F;//统计出现过几次
//创建哈夫曼树
void CreatHT(HT ht[], int n)//n为我们的初始元素数量,ht则存放我们构造的树
{
int m1node,m2node;
double min1,min2;//最小和次最小,用double,因为权重有浮点数
for(int i=0;i<2*n-1;i++)//初始化,n个叶子有2*n-1的结点,自己模拟一下就知道了
ht[i].parents=ht[i].lchild=ht[i].rchild=-1;
for(int i=n;i<2*n-1;i++)//从非叶子的第一个元素开始
{
min1=min2=MMAXX;
m1node=m2node=-1;//指向最小值和次最小值
for(int k=0;k<i;k++)
{
if(ht[k].parents==-1)//排除已经找到祖先的
{
if(ht[k].wt<min1)
{
min2=min1;m2node=m1node;//当min1为最小值的时候,min2自然就是次最小值
min1=ht[k].wt;m1node=k;
}
else if(ht[k].wt<min2){//很有意思的条件,当有数比min1大但是比min2小,更新次最小值
min2=ht[k].wt;m2node=k;//但是如果min1还可以更新,则次最小值还是min1的上一个值
}
}
}
ht[i].wt=ht[m1node].wt+ht[m2node].wt;//计算新的权重
ht[i].lchild=m1node;ht[i].rchild=m2node;//更新孩子
ht[m1node].parents=i;ht[m2node].parents=i;//更新祖先
}
}
//创建哈夫曼编码更换思路
//使用字符串数组替代结构体
void CreatHTcode(HT ht[],string s[],int n)//求哈夫曼编码
{
for (int i=0;i<n;i++)
{
//求祖先
int p=ht[i].parents,k=i;
while (p!=-1)
{
//左为0,右为1
if (ht[p].lchild == k)
s[i]+='0';//将0拼接进去
else if (ht[p].rchild== k)
s[i]+='1';//将1拼接进去
//让祖先成为孩子,再求祖先,循环
k=p;
p=ht[p].parents;
}
//反转字符串,我们是由下往上找的,最后结果是由上往下的,所以得反转
reverse(s[i].begin(),s[i].end());
}
}
求源文件的长度,求出现了哪些字符,统计出现的字符数,用上题的显然不行,因为会重复
目标是统计总共有多少个,无重复有多少个,出现的频率有多少个
int Getlength(char source[],char nortsource[],int &cl)//无重复字符的char_Length
{
//统计总共有多少字符
int length=0;
for (int i=0;source[i]!='\0';i++)//扫描
{
length++;
//如果没有出现过该字符
if (!check[source[i]])
{
//入无重复数组并改值
nortsource[cl++] = source[i];
check[source[i]] = true;
}
//统计频率
F[source[i]]++;
}
return length;
}
int restore(HT ht[],string encrypt1,char ans[],int n)//还原源码
{
int j=0;
int root=2*n-2;
for (int i=0;encrypt1[i]!='\0';i++)//循环哈夫曼编码的加密文件
{
//思路很显然了,就是我们加密思路的反过来
//看值1为左,0为右,更新节点,这里是从上往下
if (encrypt1[i]=='0')//0往左走
root=ht[root].lchild;
else if (encrypt1[i]=='1')//1右
root=ht[root].rchild;
//将所经孩子的数据存到答案数组,然后从头开始
if (check[ht[root].data])//找到唯一对应叶子
{
ans[j++]=ht[root].data;
root=2*n-2;//从头继续
}
}
return j;
}
void tx()
{
Sleep(500);cout<<".";Sleep(500);cout<<".";Sleep(500);cout<<".";
Sleep(500);cout<<".";Sleep(500);cout<<".";Sleep(500);cout<<".";
Sleep(500);cout<<".";Sleep(500);cout<<".";Sleep(500);cout<<".";
cout<<endl<<endl;
}
int main()
{
char source[10000];
char nortsource[10000];//no repeat无重复
int cl=0;
double f[10000];//存储频率
HT ht[10000];
string htc[10000];
cout<<"读取文件中";tx();
ifstream in("D:\\代码\\class1\\class\\myself\\Tree\\哈夫曼树编码\\source.txt");//源文件的位置,插入流
//报错提示
if (!in.is_open())
{
cout<<"路径错误,文档找不到!!!";
exit(0);
}
in>>source;//将文件存入source数组中
in.close();//关闭输入
//得到明文长度,出现过的字符以及长度
int n=Getlength(source,nortsource,cl);
for (int i=0;i<cl;i++)//计算出文档中每个字符出现的频率
{
f[i]=F[nortsource[i]]*1.0/n;//计算频率
ht[i].wt=f[i];//作为权重
ht[i].data =nortsource[i];
}
CreatHT(ht,cl);
CreatHTcode(ht,htc,cl);
cout<<"读取成功!!!"<<endl<<endl;Sleep(2000);
cout<<"编码成功!!!"<<endl<<endl;
cout<<"自动为您生成编码对照文件code.txt中";tx();
cout<<"已生成!!!"<<endl<<endl;
//对于每个字符生成对应的哈夫曼编码,结果保存在code.txt中
ofstream outf;
outf.open("D:\\代码\\class1\\class\\myself\\Tree\\哈夫曼树编码\\code.txt");
for (int i=0;i<cl;i++)
{
outf<<nortsource[i]<<" : "<<htc[i]<<endl;
}
outf.close();
map<char,int>loc;//存字符在nortsource出现的位置
for(int i=0;i<cl;i++)
loc[nortsource[i]]=i;
cout<<"利用哈夫曼编码为您加密文件中";tx();
cout<<"成功!!!已生成encrypt.txt!!!"<<endl<<endl;
//将源文source.txt中的每个字符用哈夫曼编码替换生成密文encrypt.txt文档
outf.open("D:\\代码\\class1\\class\\myself\\Tree\\哈夫曼树编码\\encrypt.txt");
for(int i=0;source[i]!='\0';i++)
outf<<htc[loc[source[i]]];
outf.close();
//把密文encrypt.txt根据code.txt的编码规则还原成明文unencrypt.txt文档
cout<<"为您解密加密文件中";tx();
cout<<"成功!!!已生成unencrypt.txt!!!"<<endl<<endl;
string encrypt1;
char ans[10000];
in.clear();//清流
in.open("D:\\代码\\class1\\class\\myself\\Tree\\哈夫曼树编码\\encrypt.txt");
in>>encrypt1;
in.close();
int k=restore(ht,encrypt1,ans,cl);
outf.open("D:\\代码\\class1\\class\\myself\\Tree\\哈夫曼树编码\\unencrypt.txt");
for(int i=0;i<k;i++)
outf<<ans[i];
outf.close();
cout<<"为您结束进程!"<<endl;
return 0;
}
重点看
//创建哈夫曼树
void CreatHT(HT ht[], int n)//n为我们的初始元素数量,ht则存放我们构造的树
{
int m1node,m2node;
double min1,min2;//最小和次最小,用double,因为权重有浮点数
for(int i=0;i<2*n-1;i++)//初始化,n个叶子有2*n-1的结点,自己模拟一下就知道了
ht[i].parents=ht[i].lchild=ht[i].rchild=-1;
for(int i=n;i<2*n-1;i++)//从非叶子的第一个元素开始
{
min1=min2=MMAXX;
m1node=m2node=-1;//指向最小值和次最小值
for(int k=0;k<i;k++)
{
if(ht[k].parents==-1)//排除已经找到祖先的
{
if(ht[k].wt<min1)
{
min2=min1;m2node=m1node;//当min1为最小值的时候,min2自然就是次最小值
min1=ht[k].wt;m1node=k;
}
else if(ht[k].wt<min2){//很有意思的条件,当有数比min1大但是比min2小,更新次最小值
min2=ht[k].wt;m2node=k;//但是如果min1还可以更新,则次最小值还是min1的上一个值
}
}
}
ht[i].wt=ht[m1node].wt+ht[m2node].wt;//计算新的权重
ht[i].lchild=m1node;ht[i].rchild=m2node;//更新孩子
ht[m1node].parents=i;ht[m2node].parents=i;//更新祖先
}
}
和这个
//创建哈夫曼编码更换思路
//使用字符串数组替代结构体
void CreatHTcode(HT ht[],string s[],int n)//求哈夫曼编码
{
for (int i=0;i<n;i++)
{
//求祖先
int p=ht[i].parents,k=i;
while (p!=-1)
{
//左为0,右为1
if (ht[p].lchild == k)
s[i]+='0';//将0拼接进去
else if (ht[p].rchild== k)
s[i]+='1';//将1拼接进去
//让祖先成为孩子,再求祖先,循环
k=p;
p=ht[p].parents;
}
//反转字符串,我们是由下往上找的,最后结果是由上往下的,所以得反转
reverse(s[i].begin(),s[i].end());
}
}
虽然我最早期的写法是这样子的
//哈夫曼编码(选做题)
#include<bits/stdc++.h>
#include<windows.h>
using namespace std;
#define MMAXX 0x3fffffff
typedef struct//定义叶子的属性
{
char data;// 结点值
double wt;//权重(其实就是路径长度)
int parents;//祖先
int lchild;//左右孩子
int rchild;
}HT;
typedef struct
{
char cd[200];//存放哈夫曼编码,这里的值其实是取决于我们的最后的叶子结点数的,这里不重复的字符数量不能超过200
int start;//所以start用于指向最开始的节点
}HC;
//两个STL
map<char, bool>check;//统计是否出现过
map<char, int>F;//统计出现过几次
//创建哈夫曼树
void CreatHT(HT ht[], int n)//n为我们的初始元素数量,ht则存放我们构造的树
{
int m1node,m2node;
double min1,min2;//最小和次最小,用double,因为权重有浮点数
for(int i=0;i<2*n-1;i++)//初始化,n个叶子有2*n-1的结点,自己模拟一下就知道了
ht[i].parents=ht[i].lchild=ht[i].rchild=-1;
for(int i=n;i<2*n-1;i++)//从非叶子的第一个元素开始
{
min1=min2=MMAXX;
m1node=m2node=-1;//指向最小值和次最小值
for(int k=0;k<i;k++)
{
if(ht[k].parents==-1)//排除已经找到祖先的
{
if(ht[k].wt<min1)
{
min2=min1;m2node=m1node;//当min1为最小值的时候,min2自然就是次最小值
min1=ht[k].wt;m1node=k;
}
else if(ht[k].wt<min2){//很有意思的条件,当有数比min1大但是比min2小,更新次最小值
min2=ht[k].wt;m2node=k;//但是如果min1还可以更新,则次最小值还是min1的上一个值
}
}
}
ht[i].wt=ht[m1node].wt+ht[m2node].wt;//计算新的权重
ht[i].lchild=m1node;ht[i].rchild=m2node;//更新孩子
ht[m1node].parents=i;ht[m2node].parents=i;//更新祖先
}
}
void CreatHTcode(HT ht[],HC pcd[],int n)//n同上表示叶子结点个数
{
int c,p;//child和parents
HC hcd;//存储每个叶子节点,最后汇总到cd
for(int i=0;i<n;i++)
{
//让孩子为当前节点
hcd.start=n;c=i;//因为我们的编码是从下往上的,所以一开始指向n
//求祖先
p=ht[i].parents;
while(p!=-1){
//左为0,右为1
if(ht[p].lchild==c)
hcd.cd[hcd.start--]='0';
else
hcd.cd[hcd.start--]='1';
//让祖先成为孩子,再求祖先,循环
c=p;p=ht[p].parents;
}
//让start指针指回第一个
hcd.start++;
pcd[i]=hcd;//存放该叶子点的编码
}
}
求源文件的长度,求出现了哪些字符,统计出现的字符数,用上题的显然不行,因为会重复
目标是统计总共有多少个,无重复有多少个,出现的频率有多少个
int Getlength(char source[],char nortsource[],int &cl)//无重复字符的char_Length
{
//统计总共有多少字符
int length=0;
for (int i=0;source[i]!='\0';i++)//扫描
{
length++;
//如果没有出现过该字符
if (!check[source[i]])
{
//入无重复数组并改值
nortsource[cl++] = source[i];
check[source[i]] = true;
}
//统计频率
F[source[i]]++;
}
return length;
}
int restore(HT ht[],string encrypt1,char ans[],int n)//还原源码
{
int j=0;
int root=2*n-2;
for (int i=0;encrypt1[i]!='\0';i++)//循环哈夫曼编码
{
if (encrypt1[i]=='0')//0往左走
root=ht[root].lchild;
else if (encrypt1[i]=='1')//1右
root=ht[root].rchild;
if (check[ht[root].data])//找到唯一对应叶子
{
ans[j++]=ht[root].data;
root=2*n-2;//从头继续
}
}
return j;
}
void tx()
{
Sleep(500);cout<<".";Sleep(500);cout<<".";Sleep(500);cout<<".";
Sleep(500);cout<<".";Sleep(500);cout<<".";Sleep(500);cout<<".";
Sleep(500);cout<<".";Sleep(500);cout<<".";Sleep(500);cout<<".";
cout<<endl<<endl;
}
int main()
{
char source[10000];
char nortsource[10000];//no repeat无重复
int cl=0;
double f[10000];//存储频率
HT ht[10000];
HC htc[1400];//主要爆数组的原因,不能太大
cout<<"读取文件中";tx();
ifstream in("D:\\代码\\class1\\class\\myself\\Tree\\哈夫曼树编码\\source.txt");//源文件的位置,插入流
//报错提示
if (!in.is_open())
{
cout<<"路径错误,文档找不到!!!";
exit(0);
}
in>>source;//将文件存入source数组中
in.close();//关闭输入
//得到明文长度,出现过的字符以及长度
int n=Getlength(source,nortsource,cl);
for (int i=0;i<cl;i++)//计算出文档中每个字符出现的频率
{
//计算频率,以频率作为权重,最后叶子赋值
f[i]=F[nortsource[i]]*1.0/n;
ht[i].wt=f[i];
ht[i].data =nortsource[i];
}
CreatHT(ht,cl);
CreatHTcode(ht,htc,cl);//注意观察数据大小,容易爆
cout<<"读取成功!!!"<<endl<<endl;
cout<<"编码成功!!!"<<endl<<endl;
cout<<"自动为您生成编码对照文件code.txt中";tx();
cout<<"已生成!!!"<<endl<<endl;
//对于每个字符生成对应的哈夫曼编码,结果保存在code.txt中
ofstream outf;
//输出到文件code.txt
outf.open("D:\\代码\\class1\\class\\myself\\Tree\\哈夫曼树编码\\code.txt");
for (int i=0;i<cl;i++)
{//1
outf<<nortsource[i]<<" : ";
for(int k=htc[i].start;k<=cl;k++)
outf<<htc[i].cd[k];
outf<<endl;
}
//关闭文件
outf.close();
//存字符在nortsource出现的位置,方便寻找
map<char,int>loc;
for(int i=0;i<cl;i++)
loc[nortsource[i]]=i;//无字符数组中i的位置和i绑定
cout<<"利用哈夫曼编码为您加密文件中";tx();
cout<<"成功!!!已生成encrypt.txt!!!"<<endl<<endl;
//将源文source.txt中的每个字符用哈夫曼编码替换生成密文encrypt.txt文档
outf.open("D:\\代码\\class1\\class\\myself\\Tree\\哈夫曼树编码\\encrypt.txt");
for(int i=0;source[i]!='\0';i++){//2
//模仿输出
//对于这个字符找到他在编码中的第几位,以他的start为起点
for(int k=htc[loc[source[i]]].start;k<=cl;k++)
outf<<htc[loc[source[i]]].cd[k];
}
outf.close();
//把密文encrypt.txt根据code.txt的编码规则还原成明文unencrypt.txt文档
cout<<"为您解密加密文件中";tx();
cout<<"成功!!!已生成unencrypt.txt!!!"<<endl<<endl;
string encrypt1;
char ans[10000];
in.clear();//清理流
//打开文件
in.open("D:\\代码\\class1\\class\\myself\\Tree\\哈夫曼树编码\\encrypt.txt");
in>>encrypt1;
in.close();
int k=restore(ht,encrypt1,ans,cl);
outf.open("D:\\代码\\class1\\class\\myself\\Tree\\哈夫曼树编码\\unencrypt.txt");
for(int i=0;i<k;i++)
outf<<ans[i];
outf.close();
cout<<"为您结束进程!"<<endl;
return 0;
}
但是本质是一个道理
后期还有优化,敬请期待
下面是效果截图
这代版本的code中会出现
乱码,原因是编码不统一,建议使用UTF-8编码,后期会优化代码