通过哈夫曼树进行编码与译码,首先要明确,
* 哈夫曼编码的作用,哈夫曼编码是通过用01编码来代替原来的字符,从而实现了压缩.
* 哈夫曼编码是通过哈夫曼树中获取到的
要构建哈夫曼树,首先就需要获得所输入字符的种类,以及各种字符出现次数,从而让这些出现次数作为权值来构建哈夫曼树. 例如现在要对aadddddffff进行压缩,
* 首先获得输入字符的种类有3种,分别为a d f
* 对应的频率为:a 2;b 5;f 4
* 构建出来的哈夫曼树如图:(构建哈夫曼树的目的就是获得不同种类字符的哈夫曼编码)
获得了哈夫曼树,因此就可以获得这些字符的哈夫曼编码,所以a d f的哈夫曼编码为:
* a:11
* d:0
* f:10
可以看出哈夫曼编码就是前缀编码,因此在译码的时候不会产生错乱.
* 通过哈夫曼编码,就可以把输入的字符aadddddffff翻译成11110000010101010
获得字符的编码后,就对该编码进行译码,既然编码是通过哈夫曼树获得的,那么译码也就可以通过哈夫曼树来处理(哈夫曼树就是字符与编码的映射表)因此译码的方式可以将编码依次通过哈夫曼树来进行译码具体实现方式为: 将11110000010101010从头依次遍历i标记当前遍历到的字符,用c初始化标记哈夫曼树的根节点当str[i]为0,则c标记当前结点HuffmanTree[c]的左孩子,即c=HuffmanTree[c].lchild,如果是1则c=HuffmanTree[c].rchild按以上处理方式,一直让c标记到叶子结点,则前i个编码已经译码成功,c重新标记树根,i继续往下遍历,进行处理.
- 译码的伪代码:
while(i<codeStr.length()){ //遍历01编码字符串
c=2*n-1; //c标记哈夫曼树的根节点
while(huffmanTree[c].lchild!=0&&huffmanTree[c].rchild!=0){//只要还没遍历到根节点就继续译码
if(str[i]‘0’){ //0就指向左孩子
c=huffmanTree[c].lchild;
}else if(str[i]‘1’){ //1就指向右孩子
c=huffmanTree[c].rchld;
}
}
//以上循环结束,则说明,c已经遍历到叶子节点了,输出该叶子节点的字符,就是编码对应的一个字符
cout<<huffmanTree[c].ch;
}
*
编码与译码的完整代码:
#include <iostream>
#include<string>
#include<string.h>
#include<cstring>
#include<map>
#define MAXSIZE 100//用于定义统计字符频率数组的大小
#define MAXVALUE 0x7fffffff //定义整型变量的最大值
using namespace std;
//哈夫曼树的结点结构,最终利用该类型的数组来保存生成的哈夫曼树
typedef struct{
//结点的权值
int weight;
//该权值对应的字符
char ch;
//该结点的左孩子和右孩子以及父亲的下标,记录父亲的位置主要是用于生成哈夫曼编码
int lchild,rchild,parent;
}HTNode ,*HuffmanTree;
//定义保存每个字符的哈夫曼编码的字符串数组
typedef char **HuffmanCode;
//用来保存输入的字符
string str;
//全局变量用于统计字符出现的频率
int str_fq[MAXSIZE];
//用于保存输入的字符一共有多少种类型
int num;
//全局变量以数组形式保存生成的哈夫曼树
HuffmanTree HT;
//以字符串数组的形式来保存字符的哈夫曼编码
HuffmanCode HC;
//以map的形式保存字符的哈夫曼编码
map<char,string> huffmanCodeMap;
//将用户输入的字符串变为01编码
string huffmanCodeStr;
void printStr_fq(){
cout<<"显示输入字符的频率:"<<endl;
for(int i=0;i<MAXSIZE;i++){
if(str_fq[i]!=0){
cout<<"\n"<<(char)(i+32)<<":"<<str_fq[i]<<"\n";
}
}
}
/*统计str_fq中一共有多少种字符*/
int countType(){
//因为str_fq中下标+32代表字符的ascii码,然后存储的值为该字符出现的次数
//因此如果数组单元的值不为0,即该字符有出现
int count=0;
int len=0;
//获取数组的长度,sizeof求的是所占的字节数
if(str_fq!=NULL){
len=sizeof(str_fq)/sizeof(str_fq[0]);
}
for(int i=0;i<len;i++){
//有值,说明该字符出现过
if(str_fq[i]!=0){
count++;
}
}
return count;
}
/*
统计用户输入的字符串中,各个字符出现的频率
@return int* //返回一个整型数组,下标+32就是字符的ascii码
*/
void countChar( ){
cout<<"请输入你要编码的英文字符,空格用下划线代替:"<<endl;
//保存到全局变量
cin>>str;
cout<<"你输入的字符是:"<<endl<<str<<endl;
//给数组str_fq每个单元的值都初始化为0,表示每个字符出现的频率初始化是0
//str_fq定义为了全局变量,sizeof(str_fq)求的是str_fq占用的字节数
memset(str_fq,0,sizeof(str_fq));
for(int i=0;i<str.length();i++){
if(32<=str[i]<=127){
str_fq[str[i]-32]++;
}else{
cout<<"字符:"<<str[i]<<"不是ascii编码因此自动过滤.....^_^"<<endl;
}
}
//统计字符的种类个数
num=countType();
printStr_fq();
}
/*输出HT的叶子结点*/
void printHT(){
int len=2*num-1;
for(int i=1;i<=len;i++){
cout<<"\n"<<i<<"号字符是:"<<HT[i].ch;
cout<<" 权值:"<<HT[i].weight;
cout<<" 左孩子:"<<HT[i].lchild<<" 右孩子:"<<HT[i].rchild<<" 双亲:"<<HT[i].parent<<endl;
}
}
/*
对HT数组进行初始化
@param HT //存储哈夫曼树的数组
@param num //构造哈夫曼树的结点数
*/
void initHuffmanTree(){
//为存储哈夫曼树的数组分配单元数
int hNum=2*num-1;
//从数组的1号单元开始存
HT=new HTNode[hNum+1];
//对HT数组进行初始化
for(int i=1;i<=hNum;i++){
HT[i].lchild=0;
HT[i].rchild=0;
HT[i].parent=0;
HT[i].weight=0;
HT[i].ch='\0';
}
int j=1;
//将str_fq中的数据存到HT中,作为构造哈夫曼树的结点
//变量全局变量str_fq
for(int i=0;i<MAXSIZE;i++){
if(str_fq[i]!=0){
char ch=(char)(i+32);
HT[j].ch=ch;
HT[j].weight=str_fq[i];
j++;
}
}
// printHT(HT,num);
}
/*
@param len //从数组的前len个数开始处理
@param min1 //保存最小的一个数的下标
@param min2 //保存第二小的数的下标
*/
void selectMin(int len ,int &min1 ,int &min2){
//初始化为整型最大值,用于比较
int minWeight1=MAXVALUE;
int minWeight2=MAXVALUE;
min1=1;
min2=1;
for(int i=1;i<=len;i++){
if(HT[i].weight<minWeight1&&HT[i].parent==0){//parent=0,该值,还没进行过处理,处理过的就不用进行处理了
//min1记录的是当前最小值的下标,所以当有比当前最小值还小的时候,min1就变成第二小的值了
min2=min1;
minWeight2=minWeight1;
minWeight1=HT[i].weight;
min1=i;
}
else if(HT[i].weight<minWeight2&&HT[i].parent==0){
minWeight2=HT[i].weight;
min2=i;
}
}
}
/*
通过全局变量str_fq数组,来创建哈夫曼树,最终创建的哈夫曼树存在HTNode类型的数组中
@
*/
void createHuffmanTree(){
//接收用户输入的字符并进行处理
countChar( );
//初始化哈夫曼树
initHuffmanTree();
//通过已经初始化的数据来创建哈夫曼树
//num个结点已经初始化,创建哈夫曼树,需要循环n-1次,才处理完毕
int min1,min2;//记录两个权值最小的单元下标
for(int i=num+1;i<=2*num-1;i++){
selectMin(i-1,min1,min2);
HT[i].weight=HT[min1].weight+HT[min2].weight;
HT[i].lchild=min1;
HT[i].rchild=min2;
HT[min1].parent=i;
HT[min2].parent=i;
}
cout<<"创建的哈夫曼树如下:"<<endl;
printHT();
}
/*输出哈夫曼编码*/
void showHuffmanCode(){
cout<<endl;
for(int i=1;i<=num;i++){
cout<<"字符:"<<HT[i].ch<<"的编码为"<<HC[i]<<endl;
}
}
/*
通过哈夫曼树求哈夫曼编码
*/
void getHuffmanCode(){
//用来标记当前节点和其双亲节点
int c,p;
//用来自定获取到的01编码保存在临时空间cd的位置
int start;
//用来保存所有字符的编码,从下标为1开始存,这样哈夫曼树的数组下标对应的就是编码的下标.
HC=new char*[num+1];
//临时保存当前遍历字符的编码,num个结点,最长的结点的编码个数为num-1个,因此用num个单元就足够
char *cd=new char[num];
cd[num-1]='\0';
for(int i=1;i<=num;i++){
//每次处理,指示位置都初始化为临时空间的最后一个单元
start=num-1;
c=i;
p=HT[i].parent;
while(p!=0){
//用来指示本次循环获得的编码的存放下标;
start--;
if(HT[p].lchild==c){
//左孩子则存0
cd[start]='0';
}else{
//右孩子则存1
cd[start]='1';
}
//继续逆向往上处理
c=p;
p=HT[p].parent;
}
/*--------以字符串数组的形式保存哈弗曼编码----------*/
//为当前编码分配合适空间
HC[i]=new char[num-start];
//把临时的空间复制到汇总的空间中
strcpy(HC[i],&cd[start]);
/*------以键值对的形式保存字符的哈夫曼编码---------------*/
huffmanCodeMap[HT[i].ch]=HC[i];
// cout<<"\n输出map:"<<huffmanCodeMap[HT[i].ch]<<endl;
}
//释放临时空间
delete cd;
}
//把用户输入的字符进行编码
void codingForStr(){
//让所有字符以01编码保存到hufmanCodeStr中
huffmanCodeStr="";
for(int i=0;i<str.length();i++){
huffmanCodeStr+=huffmanCodeMap[str[i]];
}
cout<<"输入的字符为:"<<str;
cout<<"\n以哈夫曼编码格式输出所有字符:"<<huffmanCodeStr<<endl;
}
/*通过哈夫曼树进行译码*/
void decodingByHuffmanTree(){
cout<<"下面对:\n"<<huffmanCodeStr<<"\n进行译码"<<endl;
//指示每次处理都从哈弗曼树的根节点开始
int c;
cout<<endl;
//编码字符串从0开始读取
int i=0;
//遍历整个01字符串
while(i<huffmanCodeStr.length()){
//c初始化为根节点
c=2*num-1;
//如果该编码还没译码到尽头,即HT数组中,c位单元肯定是有左右子树的,
while(HT[c].lchild!=0&&HT[c].rchild!=0&&i<huffmanCodeStr.length()){
//如果当前编码是0则c指示当前的左孩子
if(huffmanCodeStr[i]=='0'){
c=HT[c].lchild;
i++;
}else if(huffmanCodeStr[i]=='1'){
c=HT[c].rchild;
i++;
}else{
i++;
break;
}
}
cout<<HT[c].ch;
}
if(HT[c].lchild!=0&&HT[c].rchild!=0){
cout<<"\n译码失败!"<<endl;
}else{
cout<<"\n译码成功!"<<endl;
}
}
/*
用户选择对哪些编码进行译码
*/
/* *
void selectToDecoding(){
cout<<"请选择你的译码方式:"<<endl;
cout<<"1.对刚刚的输入进行译码"<<endl;
cout<<"2.自行输入进行译码"<<endl;
int select=1;
cin>>select;
switch(select){
case 1:
decodingByHuffmanTree(huffmanCodeStr);
case 2:
cout<<"请输入你的编码:"<<endl;
string str2;
cin>>str;
decodingByHuffmanTree(str);
}
}
*/
int main()
{
createHuffmanTree();
getHuffmanCode();
showHuffmanCode();
codingForStr();
decodingByHuffmanTree();
return 0;
}