编码理论课程论文
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
解析哈夫曼编码的基本应用
【摘要】哈夫曼编码(Huffman Coding)是一种编码方式,它以哈夫曼树(最优二叉树)、带权路径长度最小的二叉树为基础,并且经常应用于数据压缩和解压缩。在计算机信息处理中,“哈夫曼编码”是一种一致性编码法(又称"熵编码法"),用于数据的无损耗压缩。利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。
【关键词】哈夫曼树压缩解压缩
前言
在一般的数据结构的书中,树的那章后面,著者一般都会介绍一下哈夫曼(HUFFMAN)树和哈夫曼编码。哈夫曼编码是哈夫曼树的一个应用。本文对哈夫曼编码的应用进行了简单的介绍,系统地概括了哈夫曼树的建立、压缩和解压缩的过程。使读者更易理解哈夫曼编码的应用,可以大大提高通信的信道利用率,缩短信息传输时间,降低传输成本。
一、哈夫曼编码的基本简介
(一)哈夫曼编码的基本介绍
哈夫曼编码(Huffman Coding)是一种编码方式,哈夫曼编码是可变字长编码
(VLC)的一种。 Huffman于1952年提出一种编码方法,该方法完全依据字符出现
概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫作
Huffman编码。以哈夫曼树─即最优二叉树,带权路径长度最小的二叉树,经常
应用于数据压缩。在计算机信息处理中,“哈夫曼编码”是一种一致性编码法(又
称"熵编码法"),用于数据的无损耗压缩。这一术语是指使用一张特殊的编码表
将源字符(例如某文件中的一个符号)进行编码。这张编码表的特殊之处在于,
它是根据每一个源字符出现的估算概率而建立起来的(出现概率高的字符使用较
短的编码,反之出现概率低的则使用较长的编码,这便使编码之后的字符串的平
均期望长度降低,从而达到无损压缩数据的目的)。这种方法是由David.A.Huffman
发展起来的。
(二)哈夫曼编码的背景
哈夫曼压缩是个无损的压缩算法,一般用来压缩文本和程序文件。哈夫曼压
缩属于可变代码长度算法一族。意思是个体符号(例如,文本文件中的字符)用
一个特定长度的位序列替代。因此,在文件中出现频率高的符号,使用短的位序
列,而那些很少出现的符号,则用较长的位序列。
二、哈夫曼编码的基本使用
(一)哈夫曼树
哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的带权路径长度记为WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。可以证明哈夫曼树的WPL 是最小的。
(二)压缩
压缩代码非常简单,首先用ASCII值初始化511个哈夫曼节点:
CHuffmanNode nodes[511];
for(int nCount = 0; nCount < 256; nCount++)
nodes[nCount].byAscii = nCount;
然后,计算在输入缓冲区数据中,每个ASCII码出现的频率:
for(nCount = 0; nCount < nSrcLen; nCount++)
nodes[pSrc[nCount]].nFrequency++;
然后,根据频率进行排序:
qsort(nodes, 256, sizeof(CHuffmanNode), frequencyCompare);
现在,构造哈夫曼树,获取每个ASCII码对应的位序列:
int nNodeCount = GetHuffmanTree(nodes);
构造哈夫曼树非常简单,将所有的节点放到一个队列中,用一个节点替换两个频率最低的节点,新节点的频率就是这两个节点的频率之和。这样,新节点就是两个被替换节点的父节点了。如此循环,直到队列中只剩一个节点(树根)。
// parent node
pNode = &nodes[nParentNode++];
// pop first child
pNode->pLeft = PopNode(pNodes, nBackNode--, false);
// pop second child
pNode->pRight = PopNode(pNodes, nBackNode--, true);
// adjust parent of the two poped nodes
pNode->pLeft->pParent = pNode->pRight->pParent = pNode;
// adjust parent frequency
pNode->nFrequency = pNode->pLeft->nFrequency +
pNode->pRight->nFrequency;
这里我用了一个好的诀窍来避免使用任何队列组件。我先前就直到ASCII码只有256个,但我分配了511个(CHuffmanNode nodes[511]),前255个记录ASCII 码,而用后255个记录哈夫曼树中的父节点。并且在构造树的时候只使用一个指针数组(ChuffmanNode *pNodes[256])来指向这些节点。同样使用两个变量来操作队列索引(int nParentNode = nNodeCount;nBackNode = nNodeCount –1)。
接着,压缩的最后一步是将每个ASCII编码写入输出缓冲区中:
int nDesIndex = 0;
// loop to write codes
for(nCount = 0; nCount < nSrcLen; nCount++)
{
*(DWORD*)(pDesPtr+(nDesIndex>>3)) |=
nodes[pSrc[nCount]].dwCode << (nDesIndex&7);
nDesIndex += nodes[pSrc[nCount]].nCodeLength;
}
(nDesIndex>>3): >>3 以8位为界限右移后到达右边字节的前面
(nDesIndex&7): &7 得到最高位.
(三)解压缩
解压缩比构造哈夫曼树要简单的多,将输入缓冲区中的每个编码用对应的A SCII码逐个替换就可以了。只要记住,这里的输入缓冲区是一个包含每个ASCII 值的编码的位流。因此,为了用ASCII值替换编码,我们必须用位流搜索哈夫曼树,直到发现一个叶节点,然后将它的ASCII值添加到输出缓冲区中:int nDesIndex = 0;
DWORD nCode;
while(nDesIndex < nDesLen)
{
nCode = (*(DWORD*)(pSrc+(nSrcIndex>>3)))>>(nSrcIndex&7);
pNode = pRoot;
while(pNode->pLeft)
{
pNode = (nCode&1) ? pNode->pRight : pNode->pLeft;