0.1) 本文总结于 数据结构与算法分析, 源代码均为原创, 旨在 理解 “Huffman编码(Huffman树)” 的idea 并用源代码加以实现;
0.2) Huffman树的构建算法属于 贪婪算法, 贪婪算法的基础知识参见: http://blog.csdn.net/pacosonswjtu/article/details/50071159
1.1)贪婪算法的第二个应用: 文件压缩;
1.1.2)看个荔枝:(使用一个标准编码方案)
设一个文件, 它只包含字符 a, e, i, s, t, 加上一些空格和 newline(换行)。 进一步设该文件有10个a、15个e、12个i、3个s、4个t、13个空格以及一个 newline, 如图10-8所示, 这个文件需要174个bits 来表示,因为有58个字符,每个字符3个bits;
1.2)现实中的事实: 文件可能相当大。 许多大文件是某个程序的输出数据,而在使用频率最大和最小之间的字符间通常存在很大的差别;
1.2.1)出现的问题:是否有可能提供一种更好的编码降低总的所需bits数量
2.2)解决方法:一般策略就是让代码的长度从字符到字符是编号不等的, 同时保证经常出现的字符其代码短;(注意, 如果所有的字符都以相同的频率出现, 那么要节省空间是不可能的)
2.3)代表字母的二进制代码用二叉树来表示:
对上图的分析(Analysis):
1.4)综上所述: 我们看到,基本的问题在于找到总价值最小的满二叉树,其中所有的字符都位于树叶上, 下图显示了简单字母表的最优树:
2.1)算法描述:
算法对一个由树组成的森林 进行。一棵树的权等于它的树叶的频率的和。任意选取最小权的两颗树T1 和 T2, 并任意形成以 T1 和 T2 为子树的新树, 将这样的过程进行 C-1 次。在算法的开始, 存在C 颗单节点树——每个字符一颗。在算法结束时得到一颗树, 这颗树就是最优哈夫曼编码树了;
2.2)看个荔枝(构建哈夫曼编码树的steps):
2.3)下面,我们验证哈夫曼算法产生最优代码的证明思路:
2.4)该算法是贪婪算法的原因在于: 在每一阶段我们都进行一次合并而没有进行全局的考虑, 我们只是选择两颗最小的树;
2.5) 如何实现?
2.6)有两个细节要考虑(details)
3.1)download source code: https://github.com/pacosonTang/dataStructure-algorithmAnalysis/tree/master/chapter10/p266_huffman
3.2)source code at a glance:(for complete code , please click the given link above)
1st func: building huffman tree
// building huffman tree
void buildHuffman()
{
ElementTypePtr temp;
char* codes;
int off;
off = 0;
codes = buildCharArray(off+1);
temp = buildElement();
initElement(temp);
while(!isEmpty(bh))
{
insertHeap(*temp, bh);
initElement(temp);
}
temp->code = '\0';
printf("\n=== the huffman tree we built just now is follows: ===\n");
printHuffmanTree(temp, 1);
printf("\n=== the huffman codes of left subtree ===\n");
printHuffmanCode(temp->left, 1, off, codes);
printf("\n=== the huffman codes of right subtree ===\n");
printHuffmanCode(temp->right, 1, off, codes);
}
2nd func: print huffman tree node
void copyCharArray(char *a, char *b, int size)
{
int i;
for(i=0; i<=size; i++)
a[i] = b[i];
}
void printHuffmanTree(ElementTypePtr root, int depth)
{
int i;
if(root)
{
for(i = 0; i < depth; i++)
printf(" ");
if(root->left!=NULL)
printf("%d\n", root->key);
else
printf("%d->%c\n", root->key, root->flag);
printHuffmanTree(root->left,depth+1);
printHuffmanTree(root->right, depth+1); // Attention: there's difference between traversing binary tree and common tree
}
else
{
for(i = 0; i < depth; i++)
printf(" ");
printf("NULL\n");
}
}
3rd func: print huffman code of every node
void printHuffmanCode(ElementTypePtr root, int depth, int off, char *codes)
{
int i;
char *innerCode = buildCharArray(off+2);
copyCharArray(innerCode, codes, off);
if(root)
{
innerCode[off] = root->code;
innerCode[++off] = '\0';
for(i = 0; i < depth; i++)
printf(" ");
if(root->left!=NULL)
printf("%d\n", root->key);
else
printf("%d->%c->%s\n", root->key, root->flag, innerCode);
printHuffmanCode(root->left,depth+1, off, innerCode);
printHuffmanCode(root->right, depth+1, off, innerCode); // Attention: there's difference between traversing binary tree and common tree
}
else
{
for(i = 0; i < depth; i++)
printf(" ");
printf("NULL\n");
}
}