哈夫曼编码
文章目录
引入
一个文件的内容信息可以用二级制字符编码的方式来表示,即每个字符用唯一的二进制串来表示,称为码字。
定长编码:每个字符用相同长度的二进制位数唯一表示。
e.g.一个含有10万个字符的文件,仅出现a-f这7个不同的字符,下表为对应出现次数和编码,000表示a,001表示b….
a | b | c | d | e | f | g | |
---|---|---|---|---|---|---|---|
出现次数(千次) | 40 | 20 | 15 | 8 | 7 | 6 | 4 |
定长编码 | 000 | 001 | 010 | 011 | 100 | 101 | 111 |
变长编码 | 0 | 110 | 111 | 1000 | 1001 | 1010 | 1011 |
根据定长编码可以计算出需要(40+15+20+8+7+6+4)*1000*3=300 000个二进制位数来编码文件。
根据变长编码可以计算出需要(40*1+20*3+15*3+8*4+7*4+6*4+4*4)*1000=245 000个二进制位数来编码。
节约了约18%的空间。
可以看出,出现次数越多的字符编码越短,出现次数越少的字符编码越长,从而实现压缩空间.
哈夫曼树
我们可以构造一个二叉树来表示定长编码和变长编码及其对应的字符。叶节点为字符,其根节点到叶节点的简单路径为码字,其中0表示向左,1表示向右。
文件的最优编码方案编码方案对应一颗满二叉树,即每个非叶子节点只有两个孩子节点。若有C个叶节点,则有C-1个内部节点。
代价
对于字母表C中的任意一个字符c,设c.freq为c出现的频率,令dT
©表示c在树中的深度,同时dT
©也等价于对应需要的编码长度。则总代价为
∑
c
属于
C
n
c
.
f
r
e
q
∗
d
T
(
c
)
\sum_{c属于C}^{n} c.freq*d_T(c)
c属于C∑nc.freq∗dT(c)
构造哈夫曼编码
哈夫曼算法通过贪心算法来构造哈夫曼树。给定一个有n个字符及其频率的集合,每次合并频率最小的两个节点,新节点的频率等于合并的两个节点的频率之和,重复执行n-1次。使用频率freq为关键字的最小优先队列Q,以方便找到频率最小的两个节点。
ps:以freq为关键字的最小优先队列,总是满足队列头部的元素的freq最小,且队列元素按freq递增排列。由于队列是先进先出,队列弹出总是先弹出队列的头部。
先伪代码表示大概流程:
C为含有n个节点的结构体数组,c.freq为频率,Q为以频率freq为关键字的最小优先队列,EXTRACT-MIN( )每次弹出最小优先队列最小的节点,INSERT( )将元素插入队列且保持Q仍然为最小优先队列。
HUFFMAN(C){
n=|C| //n为节点个数
for i=1 to n//将节点加入最小优先队列中
Q=INSERT(C[i])
for i=1 to n-1
TreeNode* z;//新建一个节点作为频率最小的俩个节点的父节点
z.left=EXTRACT-MIN(Q)
z.right=EXTRACT-MIN(Q)
z.freq=z.left.freq + z.right.freq //新节点的频率为最小的两个节点的频率和
INSERT(Q,z)//将新节点插回最小优先队列中
return EXTRACT-MIN(Q)//此时队列中只剩下一个节点,即哈夫曼树的根节点
}
流程图:
这里出现编码与开篇的变长编码不同,原因可能是左右孩子不同,或者出现频率相同的节点的合并的先后不同,共同导致的两次哈夫曼编码不同,但代价是相同的。第一次已经计算过了为245 000,第二次为(40*1+(20+15)*3+(4+6+7+8)*4)*1000=245 000,代价相同。
最小优先队列用最小二叉堆实现,则每次堆操作的时间复杂度为O(lg n),循环了n-1次,所以循环部分时间复杂度为O(n lgn),加上初始化花费时间O(n),总共需要O(n + n lg n)=O(n lg n)。
代码:
晚点吧...
O(n lgn),加上初始化花费时间O(n),总共需要O(n + n lg n)=O(n lg n)。
代码:
晚点吧...
标签:编码,哈夫曼,队列,最小,freq,节点
From: https://blog.csdn.net/2301_80489164/article/details/143351126