一、什么是哈夫曼编码?
1.文件编码
文件是由字符构成的,ACSII码中大概包含100个可打印的字符,每一个字符都有其特定的编码,扩展ACSII码为8位,也就是说不同的字符都需要用8位二进制位来编码,这是一种等长码。
在一些文件中,每个字符出现的频率是不等的,对于哪些出现频率相对较低的字符来说,用同样长度的编码就浪费了空间,因此我们要简化编码就需要根据实际情况来确定编码长度,这就是文件压缩的思想。但是我们如何将一个文本文件压缩后解压还和原来文件一样呢?我们就需要设计新的编码方法了。由于字符的编码不能出现二义性,所以每个字符的编码一定不能是其他字符的前缀,满足这个条件的编码称为前缀码,显然等长编码就是一种前缀码。
2.扩充二叉树
要使压缩后的文件更小,字符的加权长度就得是最小,二叉树给我们提供了很好的解决思路。
每当原来的二叉树中出现空子树时,就增加一个空树叶,由此生成的二叉树称为扩充二叉树。
扩充二叉树的外通路长度定义为从根节点到每个外结点的路径长度之和,给每个外结点赋一个权值,得到加权外通路长度,加权外通路长度最小的扩充二叉树称为最优二叉树。
3.哈夫曼树和哈夫曼编码
为求得最优二叉树,哈夫曼找到了一种算法,可以建立一棵最优二叉树,叫做哈夫曼树。
哈夫曼的算法:
(1)在森林中取权值最小的两个根节点a,b合并成一棵二叉树,并生成一个新节点T1作为这两个结点的父亲,T1的权值为a,b的权值和,再将生成的根节点重新放入森林。
(2)对森林重复上述操作,直到森林中只存在唯一的根节点,停止操作。
此时生成了一棵二叉树,这就是哈夫曼树。
可以证明这种算法下生成的二叉树是最优二叉树。
此外,因为每个字符(即关键词)处于二叉树的外结点的位置,所以如果按照一定的规则编码(通常为左0右1),每个字符所得到的编码一定不是其他编码的前缀,所以哈夫曼编码也是前缀码。
二、如何构建一棵哈夫曼树?
1.哈夫曼树的结点域
哈夫曼树结点至少要包含左右儿子、权重和数据信息,但是只有这些还不够,对于其他操作还不够简便,我在此加入了father指针,方便后续寻根确定对应的哈夫曼编码,加入了left_or_right字段,可以确定某个结点是其父节点的左儿子还是右儿子,也是为了方便哈夫曼编码,后续会有详细讲解。
typedef struct Huffman{
struct Huffman *Llink,*Rlink,*father; //father结点用于后期记录字符的路径
char info; //字符
int weight; //词频(权重)
char left_or_right; //这个结点是其父节点的左儿子还是右儿子,方便编码
}huffman;
2.按照算法构建哈夫曼树
bool cmp(huffman *a,huffman *b){
return a->weight<b->weight;
}
void initial(huffman *H[],int n){ //排序:升序排列
sort(H+1,H+n+1,cmp);
}
void Huffman(int n){ //构建哈夫曼树
int i,j;
for(i=1;i<=n;i++){
H[i]->Llink=NULL;
H[i]->Rlink=NULL;
}
for(i=1;i<=n-1;i++){
huffman* t=new huffman;
t->Llink=H[i]; H[i]->father=t; H[i]->left_or_right='0'; //是左儿子,标记为0
t->Rlink=H[i+1]; H[i+1]->father=t; H[i+1]->left_or_right='1'; //是右儿子,标记为1
t->weight=H[i]->weight+H[i+1]->weight; //t的权重为左右儿子权重之和
for(j=i+2;j<=n;j++){
if(t->weight>H[i]->weight) H[j-1]=H[j];
else break;
}
H[j-1]=t;
}
H[n]->father=NULL; //哈夫曼树的根节点没有父节点,赋为空
}
按照算法,每次从森林中取两个权值最小的结点连接起来形成一棵二叉树并新生成一个新的根节点,这一步我采用先对森林的各个节点按照权重大小升序排列,每次取前两个结点合并,将合并的新的根节点通过插入排序重新插入森林中,依次往后推进,直到到达最后一个结点,即为构建好的哈夫曼树的根节点。
由此我们构建了一棵根节点为H[n]的哈夫曼树。
三、接下来考虑编码和解码的工作
1.进行哈夫曼编码
要讲一个文本文件压缩,我们需要进行一系列的前期准备工作。
(1)我们需要一个哈夫曼编码的结构体
struct huffman_code{ //对应每个字符的编码结构体
char info; // 字符
char code[20]; //哈夫曼编码
};
huffman_code CODE[max];
还需要一个该结构体的数组保存所有字符的对应编码。
(2)需要将文件读入,统计词频。
count是用来统计文本中有多少中字符用的
int readin(){ //从data.txt文件中读取文章
FILE* fp;
fp=fopen("data.txt","r");
if(fp==NULL){
printf("failed to open the file!");
exit(-1);
}
int i,count;
char ch;
while((ch=fgetc(fp))!=EOF){
before++; //记录原始文件字符个数
for(i=1;i<=count;i++){ //count统