哈夫曼编码(贪心算法)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈夫曼编码(贪心算法)相关的知识,希望对你有一定的参考价值。
参考技术A参考: 哈夫曼编码
哈夫曼编码是一种十分有效的编码方法,广泛应用于 数据压缩 中
通过采用 不等长 的编码方式,根据 字符频率的不同 ,选择 不同长度的编码 ,对频率 越高 的字符采用 越短 的编码实现数据的高度压缩。
这种对频率越高的字符采用越短的编码来编码的方式应用的就是贪心算法的思想。
下面看一个例子:
假如我们有一个包含1000个字符的文件,每个字符占1个byte(1byte=8bits),则存储这100个字符一共需要8000bits。这还是有一些大的
那我们统计一下这1000个字符中总共有多少种字符,原来需要8bit来表示一个字符,如果使用更少的位数来表示这些字符,则可以减少存储空间。
假设这1000个字符中总共有a、b、c、d、e、f共6种字符,使用使用3个二进制位来表示的话,存储这1000个字符就只需要3000bits,比原来更节省存储空间。
或许还可以再压缩一下:
根据字符出现的 频率 给与字符 不等长 的编码,频率越高的字符编码越短,频率越低的字符编码越长。
它不能像等长编码一样直接按固定长度去读取二进制位,翻译成字符,为了能够准确读取翻译字符,它要求一个字符的编码不能是另外一个字符的前缀。
假设a、b、c、d、e、f这6个字符出现的频率依次降低,则我们可以给与他们这样的编码
假如字符的出现频率如图所示,按照这样的编码表示的话,总位数如图,一共2100bits,更加节省空间了
贪心策略:频率小的字符,优先入队。
步骤:
1.将每一个字符作为节点,以出现频率大小作为权重,将其都放入 优先队列 中(一个最小堆);
2.每次出队两个节点并创建一个父节点,使其权值为刚刚出队的节点的权值和,并且为两个节点的父节点(合并)。然后将这个树入队。
3.重复操作2,直到队列中只有一个元素(此时这个元素表示形式应该为一个树)时,完成创建。
创建好了树,该怎么编码呢?
我们对一个哈夫曼树,从父节点开始的所有节点,往左边标0,右边标1。那么到达叶子节点的顺次编码就可以找到了。
C:字符集合
Q:优先队列
EXTRACT-MIN:传入一个队列,出队最小的元素
INSERT:将z插入到Q中
当for循环结束之后,此时队列中只有一个元素,就是我们需要的哈夫曼树,最后返回此树即可。
假设T树已经是一个最优的树,假设x、y的频率小于等于最低处的a、b,然后交换x、a,y、b。
计算代价是否发生变化。
比如这里比较 T 变成 T ’ 后代价是否变化,发现代价变小或不变。
同理T’到T’’,又因为T本来假设就是最优的,所以只能相等
所以T’’也应该符合条件,即贪婪算法,每次取最小的两个节点出来这种做法是正确的
贪心算法 哈夫曼树编码
1 #include <stdio.h> 2 #include <string.h> 3 #define N 50 //叶子结点数 4 #define M 2*N-1 //树中结点总数 5 typedef struct 6 { 7 char data[5]; //结点值 8 int weight; //权重 9 int parent; //双亲结点 10 int lchild; //左孩子结点 11 int rchild; //右孩子结点 12 } HTNode; 13 typedef struct 14 { 15 char cd[N]; //存放哈夫曼码 16 int start; 17 } HCode; 18 void CreateHT(HTNode ht[],int n) //由ht的叶子结点构造完整的哈夫曼树 19 { 20 int i,k,lnode,rnode; 21 int min1,min2; 22 for (i=0;i<2*n-1;i++) //所有结点的相关域置初值-1 23 ht[i].parent=ht[i].lchild=ht[i].rchild=-1; 24 for (i=n;i<2*n-1;i++) //构造哈夫曼树的分支结点 25 { 26 min1=min2=32767; //lnode和rnode为最小权重的两个结点位置 27 lnode=rnode=-1; 28 for (k=0;k<=i-1;k++) //查找最小和次小的结点 29 if (ht[k].parent==-1) //只在尚未构造二叉树的结点中查找 30 { 31 if (ht[k].weight<min1) 32 { 33 min2=min1;rnode=lnode; 34 min1=ht[k].weight;lnode=k; 35 } 36 else if (ht[k].weight<min2) 37 { 38 min2=ht[k].weight;rnode=k; 39 } 40 } 41 ht[lnode].parent=i;ht[rnode].parent=i; //合并两个最小和次小的结点 42 ht[i].weight=ht[lnode].weight+ht[rnode].weight; 43 ht[i].lchild=lnode;ht[i].rchild=rnode; 44 } 45 } 46 void CreateHCode(HTNode ht[],HCode hcd[],int n) //由哈夫曼树ht构造哈夫曼编码hcd 47 { 48 int i,f,c; 49 HCode hc; 50 for (i=0;i<n;i++) //根据哈夫曼树构造所有叶子结点的哈夫曼编码 51 { 52 hc.start=n;c=i; 53 f=ht[i].parent; 54 while (f!=-1) //循环直到树根结点 55 { 56 if (ht[f].lchild==c) //处理左孩子结点 57 hc.cd[hc.start--]=‘0‘; 58 else //处理右孩子结点 59 hc.cd[hc.start--]=‘1‘; 60 c=f;f=ht[f].parent; 61 } 62 hc.start++; //start指向哈夫曼编码最开始字符 63 hcd[i]=hc; 64 } 65 } 66 void DispHCode(HTNode ht[],HCode hcd[],int n) //输出哈夫曼编码 67 { 68 int i,k; 69 int sum=0,m=0,j; 70 printf("输出哈夫曼编码: "); 71 for (i=0;i<n;i++) 72 { 73 j=0; 74 printf(" %s: ",ht[i].data); 75 for (k=hcd[i].start;k<=n;k++) 76 { 77 printf("%c",hcd[i].cd[k]); 78 j++; 79 } 80 m+=ht[i].weight; 81 sum+=ht[i].weight*j; 82 printf(" "); 83 } 84 printf("平均长度=%g ",1.0*sum/m); 85 } 86 int main() 87 { 88 int n=6,i; 89 char *str[]={"a","b","c","d","e","f"}; 90 int fnum[]={45,13,12,16,9,5}; 91 HTNode ht[M]; 92 HCode hcd[N]; 93 for (i=0;i<n;i++) 94 { 95 strcpy(ht[i].data,str[i]); 96 ht[i].weight=fnum[i]; 97 } 98 CreateHT(ht,n); 99 CreateHCode(ht,hcd,n); 100 DispHCode(ht,hcd,n); 101 return 1; 102 }
以上是关于哈夫曼编码(贪心算法)的主要内容,如果未能解决你的问题,请参考以下文章