数据压缩----霍夫曼树和霍夫曼压缩

霍夫曼压缩的思想:使用较少的比特表示出现频繁的字符而使用较多的比特表示使用较少的字符。这样表示字符串所使用的总比特数就会减少。

前提:所有字符编码都不会成为其他字符编码的前缀。使用霍夫曼树可以保证这个前提的成立。

构造霍夫曼树:

首先定义霍夫曼树的结点类

private static class Node implements Comparable<Node> {
    private final char ch;
    private final int freq;
    private final Node left, right;

    Node(char ch, int freq, Node left, Node right) {
        this.ch    = ch;
        this.freq  = freq;
        this.left  = left;
        this.right = right;
    }
    private boolean isLeaf() { return (left == null) && (right == null); }
    public int compareTo(Node that) { return this.freq - that.freq; }
}

然后构建霍夫曼树:

霍夫曼树是一个二轮算法,它需要扫描目标字符串两次才能压缩它。第一次扫描统计每个字符出现的频率,第二次扫描根据生成的编译表压缩。

构造过程如下:为每个字符创建一个独立的结点(可以看成只有一个结点的树)。首先找到两个频率最小的结点,然后创建一个以这两个结点为子结点的新节点(新节点的频率值为两个子结点频率值之和);这个操作会使森林中树的数量减一。不断重复这个过程直到只剩下一棵树为止。这样,从树的根结点到叶结点的路径就是叶结点中字符对应的霍夫曼编码。

 private static Node buildTrie(int[] freq) {
    MinPQ<Node> pq = new MinPQ<Node>();
    for (char i = 0; i < R; i++)
        if (freq[i] > 0)
            pq.insert(new Node(i, freq[i], null, null));

    while (pq.size() > 1) {
        Node left  = pq.delMin();
        Node right = pq.delMin();
        Node parent = new Node('\0', left.freq + right.freq, left, right);
        pq.insert(parent);
    }
    return pq.delMin();
}

解码操作:

根据霍夫曼树将比特流解码:根据比特流的输入从根节点向下移动(遇到0走左子结点,遇到1走右子结点),遇到叶结点后,输出该叶结点的字符并回到根结点。

 public static void expand() {
    Node root = readTrie(); 
    int length = BinaryStdIn.readInt();

    for (int i = 0; i < length; i++) {
        Node x = root;
        while (!x.isLeaf()) {
            boolean bit = BinaryStdIn.readBoolean();
            if (bit) x = x.right;
            else     x = x.left;
        }
        BinaryStdOut.write(x.ch);
   }
   BinaryStdOut.close();
}

压缩操作:

压缩操作是根据构造的编译表实现的。根据霍夫曼树建立一张字符和路径对应的二进制字符串相联系的表,然后扫描目标字符串,每读入一个字符,查表得到相应的二进制字符串并输出即可。

构建编译表:

 private static void buildCode(String[] st, Node x, String s) {
    if (!x.isLeaf()) {
        buildCode(st, x.left,  s + '0');
        buildCode(st, x.right, s + '1');
    }
    else {
        st[x.ch] = s;
    }
}

使用编译表进行压缩:

for (int i = 0; i < input.length; i++) {
        String code = st[input[i]];
        for (int j = 0; j < code.length(); j++) {
            if (code.charAt(j) == '0')
                BinaryStdOut.write(false);
            else (code.charAt(j) == '1') 
                BinaryStdOut.write(true);
        }
    }

对于任意前缀码,编码后的比特字符串长度等于霍夫曼树的加权外部路径长度。

给定一个含有r个符号的集合和它们的频率,霍夫曼算法所构造的前缀码是最优的。

猜你喜欢

转载自my.oschina.net/HuoQibin/blog/1614137