数据结构(十七)——霍夫曼编码与霍夫曼树

前言

本节中的霍夫曼树可以看作最小堆的应用。霍夫曼编码则是霍夫曼(Huffman)设计的一种压缩算法。霍夫曼编码是思想,霍夫曼树是实现。

霍夫曼编码

思想

按照字符出现的频率给字符编码,出现次数高的字符较出现次数少的字符分配的编码短。

如何实现文件压缩(长度变短)?

比如文本是由字符a,b,c,d组成的长度为1000的字符串,如果不压缩,要占1000个字节,8000位。我现在给a、b、c、d分别分配代码00、01、10、11。那么只需要2 * 1000 + 5 * 8+4 * 2 = 2048位。
(其中多出的48位是编码表的长度,符号个数+字符a+字符b+字符c+字符d = 58。00、01、10、11占42位)

定长与变长

刚刚举的例子为定长编码,霍夫曼树表达的是变长编码。我们可以想象这样一种情况,以上一千个字符中,a出现997次,b、c、d各出现1次。如果使用编码(a = 0,b = 11,c=100,d=101)那么只需要997+2+1*2+5 * 8+1+2 * 2+3 = 1049位。

这样编码也引出了一个问题,如何解码?
定长可以每次提出2位,不定长怎么提呢。如果仔细观察的话会发现使用的不定长编码前缀都不相同,可以确保读出的字符唯一。我们可以模仿读出试一下,011100101

第一位 ——0—— a
第二位——1—— 无(读出下一位时加上)
第三位——11——b
第四位——1——无
第五位——10——无
第六位——100——c
第七位——1——无
第八位——10——无
第九位——101——d

解码出唯一的字符串abcd

扩展二叉树

从一颗树的根结点开始,0表示向左移动,1表示向右移动。上述编码可形成一颗扩展二叉树。

WEP

如果说上面的不定长编码有什么性质,那便是在形成的扩展二叉树中,这样编码的树加权外部路径长度(weighted external path length)最短。加的权便是字符出现的频率,出现频率越高的字符,分配的编码越短。上述举例的不定长编码,删除编码表的长度,便是WEP。
在这里插入图片描述
L(i)表示从根到达外部结点i的路径长度,F(i)表示字符i出现的频率。

比如下图中{a=7, b=5, c=2, d=4},分配的编码为{0, 10, 110, 111}

在这里插入图片描述

霍夫曼树

霍夫曼树是可以使WEP最小的二叉树中的其中之一。

构建霍夫曼树

原始元素是一个个只有一个结点的二叉树,从根节点中选出频率最低的两个树,作为左孩子和右孩子,它们的父结点的频率是两孩子之和。再继续从根节点中选出频率最低的两个树继续makeTree,直到最后只剩一颗树,便是霍夫曼树。
在这里插入图片描述

为什么霍夫曼树WEP最小

利用反证法证明
在这里插入图片描述
在上图中,更换不同层级的任意结点的编码会使WEP变大。

实现

/**
HuffmanTree 以及构造树的方法
*/

template <class T>
class Huffman
{
    friend BinaryTree<int> HuffmanTree(T[], int);
public:
    operator T() const{return weight;}
private:
    BinaryTree<int> tree; //使用之前定义的二叉树类
    T weight;
};

template <class T>
BinaryTree<int> HuffmanTree(T a[], int n)
{
    Huffman<T> *hNode = new Huffman<T>(n+1);

    BinaryTree<int> hTree;
    BinaryTree<int> emptyTree;

    for(int i = 1; i <= n; i++)
    {
        hTree.makeTree(i, emptyTree, emptyTree); //因为是扩展二叉树,emptyTree为外部节点,且maketree返回一个新对象
        hNode->tree = hTree;
        hNode->weight = a[i];
    }

    minHeap<Huffman<T>> heap(1);
    heap.initilize(hNode, n, n);

    Huffman<T> x,y;
    for(i=1; i <= n-1; i++)
    {
        x= heap.pop();
        y = heap.pop();
        hTree.makeTree(0,x,y);
        x.weight += y.weight;
        x.tree = hTree;
        heap.push(x);

        delete x.tree; // makeTree产生的是新对象,所以合并前的对象需要手动释放空间。
        delete y.tree;
    }
    // 最后一颗树
    x = heap.pop();

    return x.tree;
}


时间复杂度

创建hNode需要时间O(n),第一个for循环需要时间O(n)。在第二个for循环中,有2(n-1)次的pop()操作,和(n-1)次的push操作,时间复杂度为O(nlogn)。构造huffmanTree总时间为O(nlogn)。

猜你喜欢

转载自blog.csdn.net/qq_41882686/article/details/107433598
今日推荐