哈夫曼树(最优二叉树)与哈夫曼编码
什么是哈夫曼树(Huffman Tree)?
编码这个问题是计算机里面最最基础的一个,也是满核心的一个问题,像我们编码我们有很多种方式,比方说我们的整数我们用二进制的形式0101存在我们计算机里面。那么我们有一种编码呢是叫等长码,每个ASCII字符它可以编码,用7位来进行编码。如果有一篇文章他由若干个字符所构成的,每个abcd两者都用7位来进行编码,我这篇文章有1万个字符,那么就是要7万位进行编码,当然计算机里面现在我们存储起来实际上一个ASCIl码占一个字节,也就是说总共占8位,最高那位是0的。那么一个字符占8位,我有1万个字符所构成的一篇文章就是总共占用的是10,000个字节。而实际上呢,我们每个字符出现的频率是不一样的,比如e这个字符出现的频率在文章里出现的频率要大于很多字符的频率,如果我们也能够有不等长编码,比如说我们的e不要用7位进行编码,你既然出现的频率高那么我用5位进行编码,频率出现没那么高的用6位7位8位甚至9位10位都没关系的,这样的话他的效率就能得到提高。所以由于这样的一种背景呢,我应该怎么进行编码,我已知的频率不一样,我们应该怎么样进行编码能达到比较好的效果,所以这就是哈夫曼这个树跟哈夫曼编码所涉及到的一个重要的问题。
当上述判定树中60分以下的人特别少,90分以上的人特别多的时候,判别90分以上的要4步,而判别60分以下的需要1步,所以上面的判定树不是很好。
查找效率为3.15比较大,所以上面的查找效率很低。
上面的查找效率2.2,查找效率更高一些。
启示:
我们可以有不同的方法来构造搜索树,而不同搜索树它的效率是不一样的。
怎么根据不同的频率(权值)来构造一个效率,比较好甚至是最好的搜索树,这个就是哈夫曼树要解决的问题。
带权路径长度(WPL)、哈夫曼树(最优二叉树)的定义
哈夫曼树的构造
每次把权值最小的两棵二叉树合并。
就是把我们的权值从小到大进行排序,排完序之后呢,把权值最小的两个结点并在一起,形成一个新的二叉树,这个二叉树的权值就是并在一起的两个权值的和
构建哈夫曼树时,我们怎么去找两个最小的?
这个实际上就是堆要解决的问题。
把各个结点的权值构造成一个最小堆,从里面挑两个最小的并在一起形成一个新的结点,再把它插到堆里去,所以的话就用堆就很好的实现。
typedef struct TreeNode *HuffmanTree;
struct TreeNode{
int Weight;
HuffmanTree Left, Right;
}
HuffmanTree Huffman( MinHeap H )
{
/* 假设H->Size个权值已经存在H->Elements[]->Weight里 */
int i; HuffmanTree T;
BuildMinHeap(H); /*将H->Elements[]按权值调整为最小堆*/
for (i = 1; i < H->Size; i++) { /*做H->Size-1次合并*/
T = malloc( sizeof( struct TreeNode) ); /*建立新结点*/
T->Left = DeleteMin(H);
/*从最小堆中删除一个结点,作为新T的左子结点*/
T->Right = DeleteMin(H);
/*从最小堆中删除一个结点,作为新T的右子结点*/
T->Weight = T->Left->Weight+T->Right->Weight;
/*计算新权值*/
Insert( H, T ); /*将新T插入最小堆*/
}
T = DeleteMin(H);
return T;
}
哈夫曼树的特点
哈夫曼编码
前缀码可以避免二义性。
所谓前缀码就是任何字符的编码都不是另外一个字符串编码的前缀。
那么什么情况下面它会是前缀码?什么情况下它不是前缀码?
当你的所有的结点都在叶结点上的时候就不可能会出现一个字符的编码是另外一个字符的前缀。
当你的编码有一个结点出现在另外一个结点的编码的当中的时候,在树的非叶结点上的时候,就会出现前缀。
所以我们要保证我们的编码不会有二义性的一种方法就是所有的这个字符都是在二叉树叶结点上面就没问题了。
上图中a不是叶子结点,所以就出现了二义性。
所谓的找哈夫曼编码,首先要做的就是构造出哈夫曼树,然后进行编号,统一编号左子树的路径是0,右子树的路径是1。