1,霍夫曼编码描述哈夫曼树─即最优二叉树,带权路径长度最小的二叉树,经常应用于数据压缩。 在计算机信息处理中,“哈夫曼编码”是一种一致性编码法(又称“熵编码法”),用于数据的无损耗压缩。这一术语是指使用一张特殊的编码表将源字符(例如某文件中的一个符号)进行编码。这张编码表的特殊之处在于,它是根据每一个源字符出现的估算概率而建立起来的(出现概率高的字符使用较短的编码,反之出现概率低的则使用较长的编码,这便使编码之后的字符串的平均期望长度降低,从而达到无损压缩数据的目的)。这种方法是由David.A.Huffman发展起来的。 例如,在英文中,e的出现概率很高,而z的出现概率则最低。当利用哈夫曼编码对一篇英文进行压缩时,e极有可能用一个位(bit)来表示,而z则可能花去25个位(不是26)。用普通的表示方法时,每个英文字母均占用一个字节(byte),即8个位。二者相比,e使用了一般编码的1/8的长度,z则使用了3倍多。若能实现对于英文中各个字母出现概率的较准确的估算,就可以大幅度提高无损压缩的比例。2,问题描述
霍夫曼编码前首先要统计每个字的字频,即出现次数,例如: 1、将所有字母出现的次数以从小到大的顺序排序,如上图2、每个字母都代表一个终端节点(叶节点),比较F.O.R.G.E.T五个字母中每个字母的出现频率,将最小的两个字母频率相加合成一个新的节点。如上图所示,发现F与O的频率最小,故相加2+3=5,将F、O组成一个树,F为左节点,O为右节点,(FO)为根节点,每个节点的取值为其出现频率(FO的出现频率为5)3、比较5.R.G.E.T,发现R与G的频率最小,故相加4+4=8,将RG组成一个新的节点4、比较5.8.E.T,发现5与E的频率最小,故相加5+5=10,因此将FO作为左节点,E作为右节点,FOE作为根节点5、比较8.10.T,发现8与T的频率最小,故相加8+7=15,将RG作为左节点,T作为右节点,RGT作为根节点6、最后剩10.15,没有可以比较的对象,相加10+15=25,FOE作为左节点,RGT作为右节点根节点不取值,每个左子节点取值0,右子节点取值1,将每个字母从根节点开始遍历,沿途的取值组成编码: 首先选择一个文本,统计每个字符出现的次数,组成以下数组:
typedef struct FrequencyTreeNode {
int freq;
char c;
struct FrequencyTreeNode *left;
struct FrequencyTreeNode *right;
} FrequencyTreeNodeStruct, *pFrequencyTreeNodeStruct;然后将获得的数组frequencies进行排序,按照freq由小到大的顺序组成一个二叉查找树,FrequencyTreeNodeStruct,从二叉查找树中找到最小的节点,从树中删除,再取最小的节点,两个子节点,组成一个新的树,根节点c为0,freq为两个子节点的和,加入frequencies中,并排序,重复该步骤,一直到frequencies中只有一个节点,则该节点为Huffman coding tree的根节点以short类型按照前述的规则为每个字符编码,尔后将文本翻译为Huffman coding,再通过Huffman coding tree进行解码,验证编码的正确性。3,代码实现#include<stdio.h>#define n 5 //叶子数目#define m (2*n-1) //结点总数#define maxval 10000.0#define maxsize 100 //哈夫曼编码的最大位数 //定义结构体typedef struct FrequencyTreeNode { int freq; char c; struct FrequencyTreeNode *left; struct FrequencyTreeNode *right;} FrequencyTreeNodeStruct, *pFrequencyTreeNodeStruct; FrequencyTreeNodeStruct frequencies[MAXALPABETNUM]; typedef struct{ char bits[n]; //位串 int start; //编码在位串中的起始位置 char ch; //字符}codetype; // 读取文件内容,统计字符以及出现频率void readTxtStatistics(char* fileName){ unsigned int nArray[52] = {0}; unsigned int i, j; char szBuffer[MAXLINE]; int k=0; // 读取文件内容 FILE* fp = fopen(fileName, "r"); if (fp != NULL) { /*读取文件内容,先统计字母以及出现次数*/ while(fgets(szBuffer, MAXLINE, fp)!=NULL) { for(i = 0; i < strlen(szBuffer); i++) { if(szBuffer[i] <= "Z" && szBuffer[i] >= "A") { j = szBuffer[i] - "A"; } else if(szBuffer[i] <= "z" && szBuffer[i] >= "a") { j = szBuffer[i] - "a" + 26; } else continue; nArray[j]++; } } // 然后赋值给frequencies数组 for(i = 0, j = "A"; i < 52; i++, j++) { if (nArray[i] >0) { /*****/ frequencies[k].c=j; frequencies[k].freq=nArray[i]; frequencies[k].left=NULL; frequencies[k].right=NULL; k++; printf("%c:%d\n", j, nArray[i]); } if(j == "Z") j = "a" - 1; } }} //建立哈夫曼树void huffMan(frequencies tree[]){ int i,j,p1,p2;//p1,p2分别记住每次合并时权值最小和次小的两个根结点的下标 float small1,small2,f; char c; for(i=0;i<m;i++) //初始化 { tree[i].parent=0; tree[i].lchild=-1; tree[i].rchild=-1; tree[i].weight=0.0; } printf("【依次读入前%d个结点的字符及权值(中间用空格隔开)】\n",n); //读入前n个结点的字符及权值 for(i=0;i<n;i++) { printf("输入第%d个字符为和权值",i+1); scanf("%c %f",&c,&f); getchar(); tree[i].ch=c; tree[i].weight=f; } //进行n-1次合并,产生n-1个新结点 for(i=n;i<m;i++) { p1=0;p2=0; //maxval是float类型的最大值 small1=maxval;small2=maxval; //选出两个权值最小的根结点 for(j=0;j<i;j++) { if(tree[j].parent==0) if(tree[j].weight<small1) { small2=small1; //改变最小权、次小权及对应的位置 small1=tree[j].weight; p2=p1; p1=j; } else if(tree[j].weight<small2) { small2=tree[j].weight; //改变次小权及位置 p2=j; } tree[p1].parent=i; tree[p2].parent=i; tree[i].lchild=p1; //最小权根结点是新结点的左孩子 tree[i].rchild=p2; //次小权根结点是新结点的右孩子 tree[i].weight=tree[p1].weight+tree[p2].weight; } }} //根据哈夫曼树求出哈夫曼编码,code[]为求出的哈夫曼编码,tree[]为已知的哈夫曼树void huffmancode(codetype code[],frequencies tree[]){ int i,c,p; codetype cd; //缓冲变量 for(i=0;i<n;i++) { cd.start=n; cd.ch=tree[i].ch; c=i; //从叶结点出发向上回溯 p=tree[i].parent; //tree[p]是tree[i]的双亲 while(p!=0) { cd.start--; if(tree[p].lchild==c) cd.bits[cd.start]="0"; //tree[i]是左子树,生成代码"0" else cd.bits[cd.start]="1"; //tree[i]是右子树,生成代码"1" c=p; p=tree[p].parent; } code[i]=cd; //第i+1个字符的编码存入code[i] }} //根据哈夫曼树解码void decode(hufmtree tree[]){ int i,j=0; char b[maxsize]; char endflag="2"; //电文结束标志取2 i=m-1; //从根结点开始往下搜索 printf("输入发送的编码(以"2"为结束标志):"); gets(b); printf("编码后的字符为"); while(b[j]!="2") { if(b[j]=="0") i=tree[i].lchild; //走向左子节点 else i=tree[i].rchild; //走向右子节点 if(tree[i].lchild==-1) //tree[i]是叶结点 { printf("%c",tree[i].ch); i=m-1; //回到根结点 } j++; } printf("\n"); if(tree[i].lchild!=-1&&b[j]!="2") //文本读完,但尚未到叶子结点 printf("\nERROR\n"); //输入文本有错} void main(){ printf("---------------—— 哈夫曼编码实战 ——\n"); printf("总共有%d个字符\n",n); frequencies tree[m]; codetype code[n]; int i,j;//循环变量 huffMan(tree);//建立哈夫曼树 huffmancode(code,tree);//根据哈夫曼树求出哈夫曼编码 printf("【输出每个字符的哈夫曼编码】\n"); for(i=0;i<n;i++) { printf("%c: ",code[i].ch); for(j=code[i].start;j<n;j++) printf("%c ",code[i].bits[j]); printf("\n"); } printf("【读入内容,并进行编码】\n"); // 开始编码 decode(tree);}
C++ Primer Plus 第6版 中文版 清晰有书签PDF+源代码 http://www.linuxidc.com/Linux/2014-05/101227.htm读C++ Primer 之构造函数陷阱 http://www.linuxidc.com/Linux/2011-08/40176.htm读C++ Primer 之智能指针 http://www.linuxidc.com/Linux/2011-08/40177.htm读C++ Primer 之句柄类 http://www.linuxidc.com/Linux/2011-08/40175.htm
将C语言梳理一下,分布在以下10个章节中:- Linux-C成长之路(一):Linux下C编程概要 http://www.linuxidc.com/Linux/2014-05/101242.htm
- Linux-C成长之路(二):基本数据类型 http://www.linuxidc.com/Linux/2014-05/101242p2.htm
- Linux-C成长之路(三):基本IO函数操作 http://www.linuxidc.com/Linux/2014-05/101242p3.htm
- Linux-C成长之路(四):运算符 http://www.linuxidc.com/Linux/2014-05/101242p4.htm
- Linux-C成长之路(五):控制流 http://www.linuxidc.com/Linux/2014-05/101242p5.htm
- Linux-C成长之路(六):函数要义 http://www.linuxidc.com/Linux/2014-05/101242p6.htm
- Linux-C成长之路(七):数组与指针 http://www.linuxidc.com/Linux/2014-05/101242p7.htm
- Linux-C成长之路(八):存储类,动态内存 http://www.linuxidc.com/Linux/2014-05/101242p8.htm
- Linux-C成长之路(九):复合数据类型 http://www.linuxidc.com/Linux/2014-05/101242p9.htm
- Linux-C成长之路(十):其他高级议题
本文永久更新链接地址:http://www.linuxidc.com/Linux/2014-12/111066.htm