在介绍Huffman编码前,先介绍下Huffman树。
1. Huffman树
树是一种重要的非线性数据结构,它是数据元素(树中称为节点)按分支关系组织起来的结构,若干棵互不相交的树所构成的集合称为森林,树中有几个重要的概念,在这里做一下简单的介绍:
-
路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子节点之间的通路,称为路径。通路中的分支的数目称为路径长度,若规定根结点的层号是1,则从根结点到第L层结点的路径长度为L-1。
-
结点的权和带权路径长度:若为树中的结点赋予了一个具有某种含义的数值,这个数值称为结点的权,结点的带权路径长度是指,从根结点到该结点之间的路径长度与该结点权的乘积。
-
树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和。例如下图中的带权路径长度 = 1 ∗ 9 + 2 ∗ 5 + 3 ∗ 2 + 4 ∗ 1 + 4 ∗ 2 = 37 1*9 + 2*5 + 3*2 + 4*1 + 4*2 =37 1∗9+2∗5+3∗2+4∗1+4∗2=37
二叉树:二叉树是每个结点最多有两个子树的有序树,这里的有序指的是两个子树有左右之分,顺序不能颠倒,这两个子树通常称为左子树和右子树。
Huffman树:给定 n n n个权值和 n n n个叶子结点,构造一棵二叉树,若它的带权路径长度之和达到最小,则这样的二叉树为最优二叉树,也称为Huffman树。
2. Huffman树的构造
Huffman树的建立过程如下:
输入:权值为( w 1 w_{1} w1, w 2 w_{2} w2, … w n w_{n} wn)的𝑛个节点
输出:对应的Huffman树
- 将( w 1 w_{1} w1, w 2 w_{2} w2, … w n w_{n} wn)看做是有𝑛棵树的森林(若干棵互不相交的树所构成的集合称为森林),每个树仅有一个节点。
- 在森林中选择根节点权值最小的两棵树进行合并,得到一个新的树,这两颗树分布作为新树的左右子树。新树的根节点权重为左右子树的根节点权重之和。
- 将之前的根节点权值最小的两棵树从森林删除,并把新树加入森林。
- 重复步骤2)和3)直到森林里只有一棵树为止。
下面我们用一个具体的例子来说明霍夫曼树建立的过程,假设2014年世界杯期间,从新浪微博中抓取了若干条与足球相关的微博,经统计,“我”,“喜欢”,“观看”,“巴西”,“足球”,“世界杯”这六个词出现的次数分别为15,8,6,5,3,1。请以这个6个词为叶子结点,以相应的词频当权值,构造一棵Huffman树。
首先是最小的“足球”,“世界杯”合并,得到的新树根节点权重是4。此时森林里5棵树,根节点权重分别是15,8,6,5,4。此时根节点权重最小的5,4合并,得到新子树,依次类推,最终得到下面的霍夫曼树:
如果(15, 8, 6, 5, 3, 1)表示词频的话,可看到词频越大离根节点越近。
若叶子节点的个数为 n n n,则构造的huffman树中新增节点的个数为 n − 1 n-1 n−1。
3. Huffman编码
在数据通信中,需要将传送的文字转换成二进制的字符串,用0,1码的不同排列来表示字符。例如,需传送的报文为“AFTER DATA EAR ARE ART AREA”,这里用到的字符集为“A,E,R,T,F,D”,各字母出现的次数为{8,4,5,3,1,1}。现要求为这些字母设计编码。要区别6个字母,最简单的二进制编码方式是等长编码,固定采用3位二进制( 2 3 > 6 2^3 > 6 23>6),可分别用000、001、010、011、100、101对“A,E,R,T,F,D”进行编码发送,当对方接收报文时再按照三位一分进行译码。
显然编码的长度取决报文中不同字符的个数。若报文中可能出现26个不同字符,则固定编码长度为5( 2 5 > 32 2^5 > 32 25>32)。然而,传送报文时总是希望总长度尽可能短。在实际应用中,各个字符的出现频度或使用次数是不相同的,如A、B、C的使用频率远远高于X、Y、Z,自然会想到设计编码时,让使用频率高的用短码,使用频率低的用长码,以优化整个报文编码。
为使不等长编码为前缀编码(即要求一个字符的编码不能是另一个字符编码的前缀),可用字符集中的每个字符作为叶子结点生成一棵编码二叉树(把需要编码的字符放在一颗树的叶子结点,那么就不可能出现编码是别人的前缀。假如’A’的编码是’B’的编码的前缀,那么去B的过程就会经过A,那么A就不是叶子结点,与假设矛盾),为了获得传送报文的最短长度,可将每个字符的出现频率作为字符结点的权值赋予该结点上,显然字使用频率越小权值越小,权值越小叶子就越靠下,于是频率小编码长,频率高编码短,这样就保证了此树的最小带权路径长度效果上就是传送报文的最短长度。因此,求传送报文的最短长度问题转化为求由字符集中的所有字符作为叶子结点,由字符出现频率作为其权值所产生的哈夫曼树的问题。利用哈夫曼树来设计二进制的前缀编码,既满足前缀编码的条件,又保证报文编码总长最短。
下图中给出了huffman编码的示例:其中约定(词频较大的)左孩子结点编码为 1,(词频较小的)右孩子编码为 0. 这样一来,“我”、“喜欢”、“观看”、“巴西”、“足球”、“世界杯”这六个词的 Huffman 编码分别为 0,111,110,101,1001 和 1000。
注意,到目前为止,关于 Huffman 树和 Huffman 编码,有两个约定:
- 将权值大的结点作为左孩子结点,权值小的作为右孩子结点。
- 左孩子结点编码为 1, 右孩子结点编码为 0。
在 Word2vec 源码中将权值较大的孩子结点编码为 1, 较小的孩子结点编码为 0. 为与上述约定统一起见,下文中提到的“左孩子结点”都是指权值较大的孩子结点。
参考链接
- word2vec中的数学 by ptghoty