其实这个霍夫曼编码本身不是一个很难的技巧(也是霍夫曼在期末考试的过程中想出来的方案:)),因为中间用到了贪心的思想,所以也在这里列举了出来。这个问题本身在计算机系的很多教材上都出现过。这里权且记录下来。
霍夫曼的编码是这样的。假设我有一组带压缩的文本,里面各个字符出现的频率不同,现在需要对他们进行压缩。比如
假设我们有100,000个字符的文本.最直观的压缩办法就是原来每个字符要8个bits。现在我一共只有6个字符,那我就把每个字符用3个二进制位来表示,这样所有100,000个字符用300,000个bit就可以表示了。这种是最直观的方案。但是霍夫曼提出的方案更精妙一些。他提出,基于每个字符出现的频率不同,可以让出现次数多的字符用更少的二进制位来描述,出现次数少的字符用多一些二进制来描述。比如上图显示的这个Variable-length codeword里面。a出现的频率最高,所以用一个二进制位0来表示。而f出现的频率很小,所以用4个二进制位来表示。这样总共 (45 · 1 + 13 · 3 + 12 · 3 + 16 · 3 + 9 · 4 + 5 · 4) · 1,000 = 224,000 bits。可以看到这个是比原来的方案更优化的解法。
我们一样的还是用一张图来描述霍夫曼编码的流程:
这个过程概括的说就是一个根据频率建立二叉树的过程。建完之后对应的编码也就完成了。
第一步a. 这个a就和之前的活动选择问题一样,把需要的所以字符按照频率排序。【排序】
第二部b. 选取出现频率最小的两个节点 f 和 e。组成一个新的节点,新的节点的频率就是e和f的和。原来的e和f分别成了新节点的左子节点和右子节点。(注意这里一个默认的规则就是频率小的是左子节点,大的是右子节点。)然后把之前的两个节点从原来的组中删除,加新的节点加入排序。
第三部c. 其实和第二部雷同,就是一个循环的过程。这里再次去除队列中的最小频率的两项(这时是c和b)。组成新的节点加入队列排序。
如此循环往复,最后就形成了(f)这个二叉树。现在有了二叉树只有,我们把左子树这条边标记为0,右子树标记为1。这样就差生了对应的编码方式 a=0; b=101;....
比较重要的一点是哈夫曼无前缀编码的性质,我们知道如果某个字母的编码是另一个字母编码的前缀的话就会产生错误,恰恰哈弗曼树满足这个性质
#include<stdio.h> #include<stdlib.h> typedef int Data; typedef struct a { Data weight; int parent; int lchild; int rchild; }huffman; typedef char ** HuffmanCode; //找出parent为0且,权值最小的,每次获得两个最小权值 void Select(huffman *ht,int count,int *a,int *b) { int i,c; for(i=1;i<=count;i++) { if(*a==0&&(ht+i)->parent==0)//双亲节点为0且a=0 { *a=i; continue; } else if(*b==0&&(ht+i)->parent==0) { *b=i; continue; } if(((ht+i)->parent==0)&&(((ht+i)->weight<(ht+*a)->weight)||((ht+i)->weight<(ht+*b)->weight))) { if((ht+*a)->weight<(ht+*b)->weight) { *b=i; } else { *a=i; } } } if((ht+*a)->weight>(ht+*b)->weight) { c=*a; *a=*b; *b=c; } } huffman* HuffmanCoding(huffman *ht,int *w,int n)//前面的参数存放取值,共有n个字符 { int i; int *q=w; huffman *p=NULL; int a=0,b=0; int m=2*n-1; if(n<=1) { return; } ht=(huffman*)malloc((m+1)*sizeof(huffman));//0号单元不使用 分配空间 2m for(p=ht+1,i=1;i<=n;i++,p++,q++)//依次初始化 { p->lchild=0; p->parent=0; p->rchild=0; p->weight=*q; } for(;i<=m;i++,p++) { p->lchild=0; p->parent=0; p->rchild=0; p->weight=0; } for(i=n+1;i<=m;++i) { Select(ht,i-1,&a,&b); ht[a].parent=i;ht[b].parent=i;//生成一个双亲节点,,有parent ht[i].lchild=a;//左孩子 ht[i].rchild=b;//右孩子 ht[i].weight=ht[a].weight+ht[b].weight;//权值两者累加 a=0,b=0; } return ht; }