贪心算法
贪心算法是huffman编码的基石,贪心算法简而言之就是:在每一步采取比较局部的视角,选择当前问题的最优解,从而递归得到整个问题的最优解。部分动态规划的题目可以简化为贪心算法题(在证明了相应的贪心算法正确性之后)。
Huffman编码
Huffman编码是一种压缩算法,应用了贪心算法。在介绍Huffman编码以及相关的贪心原理之前我们先介绍Huffman压缩算法的原理。
对于一个纯字符文件,每个字符占用的内存为8位,一个字节。8位最多可以表示2^8种不同的字符,然而对于一个特定的文件而言,往往用不到这么多种的字符。那么,如果我们对每一个字符的编码进行改变,使每个字符的编码长度小于8位,就可以对文本起到压缩的作用。
在编码当中,前缀码可以达到最优压缩率。(CLRS p245原话,待证)所谓前缀码也就是,没有一个字符的编码会是另一个字符编码的前缀。这给我们识别不同编码提供了很大的便利。
Huffman算法应用到了最小优先队列以及二叉树的概念。在Huffman算法中我们首先把所有的字符按照出现频率从低到高放置在最小优先队列中,然后每次提取最小优先队列中最小的两个节点,把他们合并成一棵树,然后将这棵树重新插入到最小优先队列中(这棵树的频率为两个子节点的频率之和);循环往复,一直到队列中只剩下一棵树,也就是我们需要的编码树。这颗二叉树含有所有字符的编码信息:从根部开始遍历,每次转向左孩子代表1,每次转向右孩子代表0;那么一条从根部到叶节点(字符)的路径就可以代表一个由0、1构成的Huffman编码。那么我们需要证明两点:
- Huffman编码是一种前缀码。在构造树的过程中,每一个字符都是一个叶节点(与内部节点相对应)。也就是说,不存在一个叶节点的路径是另一条叶节点的路径的一部分。那么相对应的编码也就必然是前缀码。
- Huffman编码是最优前缀码。也就是说,这棵树的查找期望是最低的。我们可以发现,我们这棵树构造过程是从出现频率最小的节点开始的。直觉上你可以知道,这棵树中频率高的点在离根近的位置(也就是使得编码更短的位置),频率低的点在离根远的位置(使得编码更长的位置)。
- 更加具体的证明在CLRS上都有。
Huffman的实现
Huffman压缩
Huffman压缩分为以下几个步骤:
- 读取文本获得所有出现过的字符以及出现次数(以此统计频率)。
- 构造最小优先队列。
- 构造相应的编码树。
- 根据编码写入新的压缩文件。
读取文本
在读取文本的过程中我遍历了文件,然后用一个c++的map储存了所有出现过的字符及其出现次数。
map<char,double> alphaMap;
while(fin.get(ch))
{
if(alphaMap.count(ch))
alphaMap[ch]++;
else
alphaMap[ch]=1;
}
构造最小优先队列&构造编码树
最小队列的每一个成员不可以是一个简单的字符,它应该是一个节点或者一棵树。所以每一个成员用一个node表示,每个node包含频率值、paren节点左右孩子节点以及它还有的字符值(如果它是一个内部节点,它应该有特殊的标识表示这一点,并且不应该有一个字符值或者应该有一个很特殊的字符值)。最小队列本身如果是一个对象,他应该有extractMin(提取最小成员)、insert(插入新成员)和makeATree(Huffman算法中的核心过程,不断提取两个最小节点合并构成一棵二叉编码树)。
<