霍夫曼编码
因为使用过程中每个字符串的使用频率不同,要保证总编码长度最短,所以对应的编码长度也不相同。
1952年,数学家霍夫曼根据字符在文件中出现的频率,用0 和1组成的数字串表示字符的编码方式称为霍夫曼编码
霍夫曼编码的贪心策略
前提:将所有需要编码的字符作为叶子节点,将该字符在文件中的使用频率作为叶子节点的权值。
策略:每次从树的集合中选择没有双亲且权值最小的两颗树作为左,右子树,构造一棵新树,新树的权值为左右子树的权值之和,然后插入树的集合中
如何使用霍夫曼编码
- 初始化:将n个字符作为根节点,并赋予权值构成集合T
- 合成新树:从集合T中取出没有双亲且权值最小的两棵树ti和tj,将其合并为一颗新树zk,其权值为ti和tj之和
- 删除旧树:从集合T中删除ti和tj,并将新树zk插入T中
- 重复执行合成和删除操作直到构建完成
- 左分支编码为0,右分支编码为1,算法结束
霍夫曼编码的一般约定
-
权值最小的节点为左子树,权值次小的节点位右子树
-
如果两个节点权值相同,那么先出现的节点为左子树
-
新生成的节点排在按照生成顺序所有叶子节点的后面
例题
为了编码和译码的方便,需要从根节点出发寻找到每一条叶子节点的路径。因此要知道每个节点的权值,双亲,左子节点,右子节点,以及节点表示的字符
-
确定合适的数据结构:每个节点包括五个成员,所以定义结构体变量
typedef struct{ //结点结构体 double weight;//权值 int parent;//双亲 int lchild;//左子节点 int rchild;//右子节点 char value;//节点表示的字符 }HNodeType;
定义一个编码存储数组,但编码并不是从数组的第一个字符开始的,所有需要定义一个变量来存储编码开始时候的下标。
每个字符的编码都需要一一对应一个编码开始时候的下标,所以同样采用结构体存储数据
typedef struct{ //编码结构体 int bit[maxbit]; int start; }HCodeType;
-
初始化,依次读入权值和编码的字符串名称即可
-
循环构造霍夫曼树,不断重复从集合T中取出节点并新建树的过程且更新信息
void HuffmanTree(HNodeType HuffNode[],int n){//构造哈夫曼树 for(int i=0;i<2*n-1;i++){//初始化 HuffNode[i].weight=0; HuffNode[i].parent=-1; HuffNode[i].lchild=-1; HuffNode[i].rchild=-1; } for(int i=0;i<n;i++)//输入n个叶子结点的信息和权值 cin>>HuffNode[i].value>>HuffNode[i].weight; double m1,m2;//两个最小权值结点的权值,m1为最小,m2为次小 int x1,x2;//两个最小权值结点的编号 for(int i=0;i<n-1;i++){//执行n-1次合并 m1=m2=inf; x1=x2=-1; for(int j=0;j<n+i;j++){//查找两个无双亲且权值最小的结点 if(HuffNode[j].parent==-1&&HuffNode[j].weight<m1){ m2=m1; x2=x1; m1=HuffNode[j].weight; x1=j; } else if(HuffNode[j].parent==-1&&HuffNode[j].weight<m2){ m2=HuffNode[j].weight; x2=j; } } //更新5项信息 HuffNode[x1].parent=n+i; HuffNode[x2].parent=n+i; HuffNode[n+i].weight=m1+m2; HuffNode[n+i].lchild=x1; HuffNode[n+i].rchild=x2; } }
-
输出编码:存储时,start从n-1开始递减,从后向前存储。读取时,从start+1开始到n-1结束,从前向后输出。
void HuffmanCode(HCodeType HuffCode[],int n){//哈夫曼树编码 HCodeType cd; //定义一个临时变量来存放求解编码时的信息 int c,p; for(int i=0;i<n;i++){ c=i; p=HuffNode[c].parent; cd.start=n-1; while(p!=-1){ if(HuffNode[p].lchild==c) cd.bit[cd.start]=0; else cd.bit[cd.start]=1; cd.start--;//前移一位 c=p; p=HuffNode[c].parent; } //把叶子结点的编码信息从临时编码cd中复制出来,放入编码结构体数组 for(int j=cd.start+1;j<n;j++) HuffCode[i].bit[j]=cd.bit[j]; HuffCode[i].start=cd.start; } }