本文主要是介绍6.10哈夫曼编码与构造,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
6.10哈夫曼编码与构造
哈夫曼编码
由于哈夫曼树是具有相同叶子个数的二叉树中带权路径长度最小的二叉树,涉及求根据给定叶子(带权)求其“规模最小”的二叉树问题,用哈夫曼树构造哈夫曼编码就是其典型应用。
1.哈夫曼编码的概念
用电子方式处理符号时,需先对符号进行二进制编码。例如,在计算机中使用的英文字 符的 ASCII 编码就是 8 位二进制编码,ASCII 编码是一种定长编码,即每个字符用相同数目的二进制位编码。
为了缩短数据文件(报文)长度,可采用不定长编码。其基本思想是,给使用频度较高 的字符编以较短的编码。这是数据压缩技术的最基本的思想。如何给数据文件中的字符编以不定长编码,使各种数据文件平均最短呢?这也是个与哈夫曼树相关的最优问题。通过实例介绍概念。
- (1)前缀码:如果在一个编码系统中,任一个编码都不是其他任何编码的前缀(最左 子串),则称该编码系统中的编码是前缀码。例如,一组编码 01,001,010,100,110 就不 是前缀码,因为 01 是 010 的前缀,若去掉 01 或 010 就是前缀码。例如,名字中的郑霞、郑霞锦就不是前缀码。
- 若是前缀码,则在电文中,各字符对应的编码之间不需要分隔符。如果不是前缀码,则 若不使用分隔符,会产生二义性。
- (2)哈夫曼编码:对一棵具有 n 个叶子的哈夫曼树,若对树中的每个左分支赋予 0, 右分支赋予 1(也可规定左 1 右 0),则从根到每个叶子的通路上,各分支的赋值分别构成一 个二进制串,该二进制串就称为哈夫曼编码。
下面给出哈夫曼编码的相关特性。
定理 6-1 哈夫曼编码是前缀码。
- 证明:哈夫曼编码是根到叶子路径上的边的编码的序列,也就是等价边序列,而由树的 特点可知,若路径 A 是另一条路经 B 的最左部分,则 B 经过了 A,因此,A 的终点不是叶子。 而哈夫曼编码都对应终点为叶子的路径,所以,任一哈夫曼码都不会与任意其他哈夫曼编码 的前部分完全重叠,因此哈夫曼编码是前缀码。
定理 6-2 哈夫曼编码是最优前缀码。
即对于 n 个字符,分别以它们的使用频度为叶子权构造哈夫曼树,则该树对应的哈夫曼编码能使各种报文(由这 n 种字符构成的文本)对应的二进制串的平均长度最短。
由哈夫曼树的构造方法知,使用频度较高的字符对应的编码较短,这也直观地说明了本定理。
2.哈夫曼编码的作用
哈夫曼树被广泛的应用,最典型的就是在编码技术上的应用。利用哈夫曼树,可以得到平均长度最短的编码。
研究操作码的优化问题主要是为了缩短指令字的长度,减少程序的总长度以及增加指令所能表示的操作信息和地址信息。要对操作码进行优化,就要知道每种操作指令在程序 中的使用频率。这一般是通过对大量已有的典型程序进行统计得到的。
这里以计算机操作码的优化问题为例来分析说明。
例 6-7 设有一台模型机,共有 7 种不同的指令,其使用频率如表 6-5 所示
由于计算机内部只能识别 0、1 代码,所以若采用定长操作码,则需要 3 位(23=8)。 一段程序中若有 n 条指令,每条指令编码占 3 位,那么程序的总位数为 3×n。为了充分地 利用编码信息和减少程序的总位数,我们可以采用变长编码。如果对每一条指令指定一条编码,并使这些编码互不相同且最短,是否可以满足要求呢?可否采用如表 6-6 所示这样编码呢?
这样虽然可以使得程序的总位数达到最小,但机器却无法解码。例如,对编码串 0010110 该怎么识别呢?第一个 0 可以识别为 I1,也可以和第二个 0 组成的串 00 一起被识别为 I3, 还可以将前三位识别为 I6,这样一来,这个编码串就有多种译法。因此,若要设计变长的编 码,则这种编码必须满足这样一个条件:任意一个编码不能成为其他任意编码的前缀。把满足这个条件的编码叫做前缀编码。
利用哈夫曼算法,就可以设计出最优的前缀编码。首先,以每条指令的使用频率为权值构造哈夫曼树,据表 6-5 构造的哈夫曼树如图所示。
对于该哈夫曼树,规定向左的分支标记为 0,向右的分支标记为 1,如下图所示。这样, 从根结点开始,沿线到达各频度指令对应的叶结点,每个叶结点对应的编码长度不等,但最 长不超过 n,所经过的分支代码序列,就构成了相应频度指令的哈夫曼编码,如表 6-7 所示。
3.哈夫曼编码的算法实现
typedef char * HuffmanCode[N+1]; /* 存储哈夫曼编码串的头指针数组 */
由于每个哈夫曼编码是变长编码,因此使用指针数组存放每个编码串的头指针。
【算法描述】 求哈夫曼树的哈夫曼编码的算法
void CrtHuffmanCode(HuffmanTree ht, HuffmanCode hc, int n)
/*从叶子结点到根,逆向求每个叶子结点对应的哈夫曼编码*/
{ char *cd; cd=(char * )malloc(n* sizeof(char )); /*分配求当前编码的工作空间*/ cd[n-1]=’\0’; /*从右向左逐位存放编码,首先存放编码结束符*/ for(i=1;i<=n;i++) /*求 n 个叶子结点对应的哈夫曼编码*/ { start=n-1; /*初始化编码起始指针*/ c=i; p=ht[i].parent; /* 从叶子结点开始向上倒推 */ while(p!=0) { --start; if(ht[p].LChild==c) cd[start]=’0’; /*左分支标 0*/ else cd[start]=’1’; /*右分支标 1*/ c=p; p=ht[p].parent; /* 向上倒推 */ } hc[i]=(char *)malloc((n-start)*sizeof(char)); /*为第 i 个编码分配空间*/ strcpy(hc[i], &cd[start]); /*把编码复制到 hc[i]中*/ } free(cd);
}
这篇关于6.10哈夫曼编码与构造的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!