【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码

本次笔记内容:
5.2.1 什么是哈夫曼树
5.2.2 哈夫曼树的构造
5.2.3 哈夫曼编码

文章目录

      • 什么是哈夫曼树(Huffman Tree)
        • 例:将百分制的考试成绩转换为五分制的成绩
        • 哈夫曼树的定义
          • 带权路径长度(WPL)
          • 最优二叉树或哈夫曼树
      • 哈夫曼树的构造
        • 思路
        • 实现
      • 哈夫曼树的特点
      • 哈夫曼编码
        • 例题分析
        • 用前缀码(prefix code)避免二义性
        • 二叉树用于编码
        • 哈夫曼编码例

什么是哈夫曼树(Huffman Tree)

一个ASCII码1个字节,8位,最高位为0。但是造成一些浪费。能不能有一种编码,对于比较频繁出现的信息,用尽量少的位数去表示;不常出现的信息,用大的空间来表示?

例:将百分制的考试成绩转换为五分制的成绩

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第1张图片

如上图,这种转换规则(if-else if)对应了一棵判定树。但是如果大部分人的成绩都是81-100分,则都需要判断四次。显然,对于这种情况,该算法不好。

因此考虑学生成绩分布的频率,来进行效率指标的衡量。

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第2张图片

原判定树,效率(开销)为3.15。

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第3张图片

对于修改后的判定树,开销为2.2。显然,比修改前好。

因此,获得启示:如何根据结点不同的查找频率构造更有效的搜索树?

哈夫曼树的定义

带权路径长度(WPL)

Weighted Path Length:设二叉树有 n n n个叶子结点,每个叶子结点带有权值 w k w_k wk,从根结点到每个叶子结点的长度为 l k l_k lk,则每个叶子结点的带权路径长度之和就是: W P L = ∑ k = 1 n w k l k WPL=\sum_{k=1}^{n}w_k l_k WPL=k=1nwklk

最优二叉树或哈夫曼树

就是WPL最小的二叉树。

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第4张图片

如上,不同的二叉树构造方法,二叉树的WPL不同。

哈夫曼树的构造

思路

  • 每次把权值最小的两棵二叉树合并。

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第5张图片

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第6张图片

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第7张图片

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第8张图片

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第9张图片

步骤如上,时刻在构造左右子树结点和最小的二叉树。

实现

关键问题在于,如何选取两个最小的?

利用堆!

typedef struct TreeNode *HuffmanTree struct TreeNode
{
    int Weight;
    HuffmanTree Left, Right;
};
HuffmanTree Huffman(MinHeap H)
{
    /* 假设H->Size个权值已经存在H->Elements[]->Weight里 */
    int i;
    HuffmanTree T;
    BulidMinHeap(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;
}
// 问题:T是HuffmanTree的实例,怎能直接插入中?不应该是Insert(H,T->Weight)?
// 猜测:或许这里Insert()做了一个重载,作用为插入一个小二叉树。
// 猜测:否定上述猜测,应该写成Insert(H,T->Weight)。
// 问题:这里DeleteMin(H)的用法不对,前两次返回的是数值,最后一次返回的是结点。

/*
总结:否定之前的四行问题与猜测。无论是对于Left还是T,都是HuffmanTree的实例,
这里的Insert()、DeleteMin()并非是在“堆”课程中所讲的函数,这里它们的返回值都是
结点,而非数值。
*/

在记笔记过程中发现些问题,总结如上。

此算法的整体复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)

哈夫曼树的特点

  • 没有度为1的结点;
  • n个叶子结点的哈夫曼树共有2n-1个结点;
    • n0:叶节点的总数;
    • n1:只有1个儿子的结点总数;
    • n2:有2个儿子的结点总数;
    • n2 = n0 - 1。
  • 哈夫曼树的任意非叶结点的左右子树交换后仍是哈夫曼树;
  • 对同一组权值{ w 1 , w 2 , . . . , w n w_1,w_2,...,w_n w1,w2,...,wn},是否存在不同构的两棵哈夫曼树呢?
    • 答案是肯定的,会存在不构同的哈夫曼树,但是WPL都是最小值。

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第10张图片

如上图。

哈夫曼编码

  • 给定一段字符串,如何对字符进行编码,可以使得该字符串的编码存储空间最少?

例题分析

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第11张图片

如上图,本题目与本次笔记开头相呼应。

用前缀码(prefix code)避免二义性

前缀码:任何字符的编码都不是另一字符编码的前缀。可以无二义地解码。

二叉树用于编码

将所有字符都放在二叉树的叶结点上,可以无二义解码。

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第12张图片

由此,想起哈夫曼树。

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第13张图片

可见,Cost(WPL)小了,使用哈夫曼树效果好。

哈夫曼编码例

【数据结构笔记16】哈夫曼树,带权路径长度(WPL),哈夫曼编码_第14张图片

如上图,先根据频率构造哈夫曼树,接着得出每个字符的编码。

你可能感兴趣的:(数据结构)