什么是哈夫曼编码?

什么是哈夫曼编码?,第1张

哈夫曼编码(Huffman Coding)是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。 Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长 度最短的码字,有时称之为最佳编码,一般就叫作Huffman编码。 以哈夫曼树─即最优二叉树,带权路径长度最小的二叉树,经常应用于数据压缩。 在计算机信息处理中,“哈夫曼编码”是一种一致性编码法(又称"熵编码法"),用于数据的无损耗压缩。这一术语是指使用一张特殊的编码表将源字符(例如某文件中的一个符号)进行编码。这张编码表的特殊之处在于,它是根据每一个源字符出现的估算概率而建立起来的(出现概率高的字符使用较短的编码,反之出现概率低的则使用较长的编码,这便使编码之后的字符串的平均期望长度降低,从而达到无损压缩数据的目的)。这种方法是由David.A.Huffman发展起来的。 例如,在英文中,e的出现概率很高,而z的出现概率则最低。当利用哈夫曼编码对一篇英文进行压缩时,e极有可能用一个位(bit)来表示,而z则可能花去25个位(不是26)。用普通的表示方法时,每个英文字母均占用一个字节(byte),即8个位。二者相比,e使用了一般编码的1/8的长度,z则使用了3倍多。倘若我们能实现对于英文中各个字母出现概率的较准确的估算,就可以大幅度提高无损压缩的比例。

本文描述在网上能够找到的最简单,最快速的哈夫曼编码。本方法不使用任何扩展动态库,比如STL或者组件。只使用简单的C函数,比如:memset,memmove,qsort,malloc,realloc和memcpy。

因此,大家都会发现,理解甚至修改这个编码都是很容易的。

背景

哈夫曼压缩是个无损的压缩算法,一般用来压缩文本和程序文件。哈夫曼压缩属于可变代码长度算法一族。意思是个体符号(例如,文本文件中的字符)用一个特定长度的位序列替代。因此,在文件中出现频率高的符号,使用短的位序列,而那些很少出现的符号,则用较长的位序列。

编码使用

我用简单的C函数写这个编码是为了让它在任何地方使用都会比较方便。你可以将他们放到类中,或者直接使用这个函数。并且我使用了简单的格式,仅仅输入输出缓冲区,而不象其它文章中那样,输入输出文件。

bool CompressHuffman(BYTE *pSrc, int nSrcLen, BYTE *&pDes, int &nDesLen)

bool DecompressHuffman(BYTE *pSrc, int nSrcLen, BYTE *&pDes, int &nDesLen)

要点说明

速度

为了让它(huffman.cpp)快速运行,我花了很长时间。同时,我没有使用任何动态库,比如STL或者MFC。它压缩1M数据少于100ms(P3处理器,主频1G)。

压缩

压缩代码非常简单,首先用ASCII值初始化511个哈夫曼节点

CHuffmanNode nodes[511]

for(int nCount = 0nCount <256nCount++)

nodes[nCount].byAscii = nCount

然后,计算在输入缓冲区数据中,每个ASCII码出现的频率:

for(nCount = 0nCount <nSrcLennCount++)

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 = nNodeCountnBackNode = nNodeCount –1)。

接着,压缩的最后一步是将每个ASCII编码写入输出缓冲区中:

int nDesIndex = 0

// loop to write codes

for(nCount = 0nCount <nSrcLennCount++)

{

*(DWORD*)(pDesPtr+(nDesIndex>>3)) |=

nodes[pSrc[nCount]].dwCode <<(nDesIndex&7)

nDesIndex += nodes[pSrc[nCount]].nCodeLength

}

(nDesIndex>>3): >>3 以8位为界限右移后到达右边字节的前面

(nDesIndex&7): &7 得到最高位.

注意:在压缩缓冲区中,我们必须保存哈夫曼树的节点以及位序列,这样我们才能在解压缩时重新构造哈夫曼树(只需保存ASCII值和对应的位序列)。

解压缩

解压缩比构造哈夫曼树要简单的多,将输入缓冲区中的每个编码用对应的ASCII码逐个替换就可以了。只要记住,这里的输入缓冲区是一个包含每个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

nCode >>= 1

nSrcIndex++

}

pDes[nDesIndex++] = pNode->byAscii

}

哈夫曼编码规则:

哈夫曼编码是一种可变长度的编码方式,其特点是给予出现频率较高的字符更短的编码,以此达到压缩的目的。

拓展:

哈夫曼编码可以用于文件压缩,以有效减少文件大小。它也可以用来加速网络传输,以便更快地传输数据。此外,哈夫曼编码还可以用于错误检测和纠正,因为编码的模式可以帮助检测和纠正错误。

哈夫曼编码是一种无损压缩文件一种方法,他的思路很简单,却又十分经典,他利用的是无重复前缀这种思想,就是每个字符的前缀是唯一的,若a的编码是001,那么就不会存在另一个以001开头的编码了,因为,哈夫曼编码是以二叉树为基础实现的,而二叉树到每一个叶子节点的路径是唯一的,那么也就是说每一个字符的编码也是唯一的。

哈夫曼编码是一种变长编码,比起定长编码的ascii码来说,哈夫曼编码能节省很多的空间,因为每一个字符出现的频率不是一致的,例如在英语中,‘e’出现的次数是最高的,那么如果我把‘e’的编码定义的短一点,那么是不是比起定长编码来说,空间就减少了?

基于这种思路,哈夫曼编码的具体实现过程如下:

(1)首先统计文本中各字符出现的频率(权重)。

(2)使用这些频率(权重),构建出哈夫曼树。

(3)规定从根节点开始,向叶子节点行走,经过左子树,编码为0,右子树,编码为1,这样就能得到每一个叶子节点字符的编码值了。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/tougao/8043438.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-12
下一篇 2023-04-12

发表评论

登录后才能评论

评论列表(0条)

保存