hello everybody!你们机智大气的阿俊又回来了,最近事比较多,闲话少说,直接切入正题,聊聊如何给一篇全为英文字符的文章利用哈夫曼编码得到每个字符的最优编码,并完成解码功能,注意,这次也是用文件操作哟,今天可被二进制文件折磨惨了,不过搞懂后真好用,呜呜呜,我该不会是个受虐狂叭。。。
哈夫曼编码思想很简单,每次从已有序列中挑出两个权值最小的节点,这两个节点作为一个新根节点的左右子树,同时从原有序列删除这两个节点,重复过程,直到仅剩一棵树为止
实例图解执行过程
听起来容易,还是先上一个实例帮助大家理解算法执行过程叭
以下例子的执行过程是我在考前做得整理,时间仓促,有些粗糙,大家凑合着看









至此已构造好哈夫曼树,编码只需从叶子节点找到根,逆向输出即可;解码则从根开始寻找,0左1右,碰到叶子停止
掌握思想后,那就进入实战演练叭~
需求分析

嘿嘿嘿,不瞒你说,这是我课设的题目
- 注意一下,测试数据一定是所有的字符一定是要英文输入法能打出来的,看看差别,中文问号【?】,英文问号【?】,一不留神就会搞混,唉~,我是不会告诉你我卡在这一个小时的。。。不然一直报错我可不负责哦
- 采用文件输入,保存一开始也是件麻烦事,许多细节忽略了
模块分解
- 1,建立字频表(统计文本中每个字符出现的次数)
- 2,建立哈夫曼树,并求每个字符对应的编码
- 3,给文本编码,以二进制保存于文件code.dat
- 4,根据哈夫曼树,将code.dat解码,结果保存于recode.txt
1 数数字符出现个数呗
从前的我是读入一个字符,查找一下数组里有没有,没有的话填入,次数加1,有的话直接次数加1,由于每次都得从头找到尾,效率不高;现在的我,拥有武林秘籍–数据结构,自然采取更简便的方法,想知道吗?那请向下看
由于字符个数有限,所有英文字符不超过255,所以将字符的ASCII码作为下标,数组值作为次数,每读一个值只要将其转化为ASCII码即可(类似希尔映射)
所以关键在于如何将字符转化为ASCII码,很简单,只要将字符赋给一个整型变量,编译器自动帮你转化为ASCII码。爱思考的小伙伴就会问“那ASCII码转化为字符咋办?”,嘿嘿嘿,和之前步骤相似,将整型赋给一个字符变量,转化完成,耶~~
瞧瞧可爱的代码
//统计每个字符出现次数:只有英文符号,否则报错
void Count_Character_Occur_Frequency()
{
int cof[256];//存储相应字符出现的次数,字符ASCII为下标。charater_occur_frequency
for(int i = 0; i < 256; i++)//初始化字符出现次数统计表
{
cof[i] = 0;
}
//从源文件按行读取,并统计字符个数,由于字符个数有限,所以用字符的ASCII码作为数组下标,数组值作为次数,类似哈希映射
fstream inFile("source.txt",ios::in);
if(!inFile)cout<<"source.txt 打开失败!"<<endl;
int sum = 0;//总行数,记录换行个数
string s;//存放一行
while(true)
{
getline(inFile,s);
if(!inFile)break;//避免重复读取最后一个字符
sum++;
for(int i = 0; i < s.size(); i++)
{
int a = s[i];//cout<<"a:"<<a<<endl;中文会溢出
cof[a]++; //计数
}
}
inFile.close();//好习惯
int a = '\n';//换行符
cof[a] = sum; //换行符个数
//=======将所有出现的字符及其次数写入文件(类似全局数组)=========
int n = 0;//计算出现字符总个数
for(int i = 0; i < 256; i++)
{
if(cof[i] != 0)n++;
}
fstream outFile("字频表.txt",ios::out);//写入若是无此文件,系统自动创建
if(!outFile)cout<<"字频表.txt 打开失败!"<<endl;
outFile<<n<<endl;//写入字符总个数
//打印调试&&写入文件
for(int i = 0; i < 256; i++)
{
if(cof[i] != 0)
{
char ch = i - '\0';
// cout<<"i: "<<i<<" 字符:"<<ch<<" cof[i]: "<<cof[i]<<endl;
outFile<<i<<" "<<cof[i]<<endl;//写入文件
}
}
outFile.close();
}
2 建棵哈夫曼树玩玩
算法思想很简单,把n个带权节点看成n棵树,每次选取根节点权值最小的两棵树构建出一颗新二叉树,加入树的集合中,新二叉树的权值为左右子树根节点权值之和,同时删除被选中的两棵二叉树,此时变成n-1棵树。重复以上过程知道仅有一颗树存在,那棵树就是哈夫曼树
具体算法步骤我就不写了,最好是能跟着算法走一遍,体会一下
- 应我们老师要求:两个节点权值不同,左小右大 ;相同,下标小者在左 (大家就随意叭,不一定要这样)
- 同时寻找最小值,次小值需留个心眼
- 利用栈从叶子出发读出每个字符的编码
这步依赖于字频表的建立
//创建哈夫曼树
void CreateHT()
{
HuffmanTree HTree;
fstream inFile("字频表.txt",ios::in);
if(!inFile)cout<<"字频表.txt 打开失败!"<<endl;
int n;//节点个数
inFile>>n;
HTree = (HTNode*)malloc(2*n*sizeof(HTNode));//哈夫曼构造,共需2n-1个,0号单元不用
for(int i = 1; i < 2*n; i++)//初始化 1
{
HTree[i].ascii = HTree[i].lchild = HTree[i].parent = HTree[i].rchild = HTree[i].weight = 0;//0号单元无用
}
for(int i = 1; i <= n; i++)//初始化 2,从文件读取ASCII码及相应权值
{
inFile>>HTree[i].ascii>>HTree[i].weight;
}
inFile.close();
for(int i = n+1; i < 2*n; i++)//从n+1开始,进行n-1次计算
{
//==============寻找最小,次小值,记录其下标 =========
int min1 = MIN1,min2 = MIN2;
int index1 = 0,index2 = 0;
for(int j = 1; j < i; j++)//i是即将要被填入的根节点
{
if(HTree[j].parent == 0)//双亲为0表示尚待操作
{
if(min1 > HTree[j].weight)
{
min2 = min1;//先赋给次小值
index2 = index1;
min1 = HTree[j].weight;
index1 = j;
}
else if(min2 > HTree[j].weight)
{
min2 = HTree[j].weight;
index2 = j;
}
}
}
//==============五处状态更新==================================
HTree[i].weight = HTree[index1].weight + HTree[index2].weight;//双亲权值更新
HTree[index1].parent = HTree[index2].parent = i;//孩子的双亲节点更新
if(HTree[index1].weight < HTree[index2].weight)//1,两个节点权值不同,左小右大 ;相同,下标小者在左
{
HTree[i].lchild = index1;//下标赋值
HTree[i

本文详细介绍了如何使用哈夫曼编码对英文字符文章进行最优编码和解码的过程,包括建立字频表、构建哈夫曼树、编码文本并以二进制形式保存,最后根据哈夫曼树解码。
2800

被折叠的 条评论
为什么被折叠?



