哈夫曼树(Huffman)

哈夫曼树

Huffman 编码问题

问题引入

什么是编码?

简单说就是建立【字符】到【数字】的对应关系,如下面大家熟知的 ASC II 编码表,例如,可以查表得知字符【a】对应的数字是十六进制数【0x61】

\000102030405060708090a0b0c0d0e0f
0000000102030405060708090a0b0c0d0e0f
0010101112131415161718191a1b1c1d1e1f
002020!"#$%&()*+,-./
00300123456789:;<=>?
0040@ABCDEFGHIJKLMNO
0050PQRSTUVWXYZ[\]^_
0060`abcdefghijklmno
0070pqrstuvwxyz{|}~7f

注:一些直接以十六进制数字标识的是那些不可打印字符

传输时的编码

  • java 中每个 char 对应的数字会占用固定长度 2 个字节
  • 如果在传输中仍采用上述规则,传递 abbccccccc 这 10 个字符
    • 实际的字节为 0061006200620063006300630063006300630063(16进制表示)
    • 总共 20 个字节,不经济

现在希望找到一种最节省字节的传输方式,怎么办?

假设传输的字符中只包含 a,b,c 这 3 个字符,有同学重新设计一张二进制编码表,见下图

  • 0 表示 a
  • 1 表示 b
  • 10 表示 c

现在还是传递 abbccccccc 这 10 个字符

  • 实际的字节为 01110101010101010 (二进制表示)
  • 总共需要 17 bits,也就是 2 个字节多一点,行不行?

不行,因为解码会出现问题,因为 10 会被错误的解码成 ba,而不是 c

  • 解码后结果为 abbbababababababa,是错误的

怎么解决?必须保证编码后的二进制数字,要能区分它们的前缀(prefix-free)

用满二叉树结构编码,可以确保前缀不重复

请添加图片描述

  • 向左走 0,向右走 1
  • 走到叶子字符,累计起来的 0 和 1 就是该字符的二进制编码

再来试一遍

  • a 的编码 0
  • b 的编码 10
  • c 的编码 11

现在还是传递 abbccccccc 这 10 个字符

  • 实际的字节为 0101011111111111111(二进制表示)
  • 总共需要 19 bits,也是 2 个字节多一点,并且解码没有问题了,行不行?

这回解码没问题了,但并非最少字节,因为 c 的出现频率高(7 次)a 的出现频率低(1 次),因此出现频率高的字符编码成短数字更经济

考察下面的树

请添加图片描述

  • 00 表示 a
  • 01 表示 b
  • 1 表示 c

现在还是传递 abbccccccc 这 10 个字符

  • 实际的字节为 000101 1111111 (二进制表示)
  • 总共需要 13 bits,这棵树就称之为 Huffman 树
  • 根据 Huffman 树对字符和数字进行编解码,就是 Huffman 编解码

Huffman 树

public class HuffmanTree {

    Node root;
    String code;
    private static class Node{
        char ch;
        int freq;
        String code;
        Node left;
        Node right;

        public Node(char ch) {
            this.ch = ch;
        }

        public Node(char ch, int freq) {
            this.ch = ch;
            this.freq = freq;
        }

        public Node(int freq, Node left, Node right) {
            this.freq = freq;
            this.left = left;
            this.right = right;
        }

        public boolean isLeaf(){
            return this.left == null && this.right == null;
        }
    }

    public HuffmanTree(String s){
        char[] charArray = s.toCharArray();
        Map<String,Integer> map = new HashMap();
        for (char c : charArray) {
            Integer i = map.getOrDefault(String.valueOf(c),0);
            map.put(String.valueOf(c),i+1);
        }
        PriorityQueue<Node> queue = new PriorityQueue<>(Comparator.comparingInt(v -> v.freq));
        for (String string : map.keySet()) {
            Node node = new Node(string.charAt(0), map.get(string));
            queue.add(node);
        }
        while(queue.size() > 1){
            Node n1 = queue.poll();
            Node n2 = queue.poll();
            Node node = new Node(n1.freq + n2.freq, n1, n2);
            queue.add(node);
        }
        root = queue.peek();
        s = doEncode(root,new StringBuilder(),s);
        code = s;
    }
}

Huffman 编解码

补充两个方法,注意为了简单期间用了编解码都用字符串演示,实际应该按 bits 编解码

public class HuffmanTree {
    // ...

    // 编码
    private String doEncode(Node node,StringBuilder sb,String s){
        if(!node.isLeaf()){
            s = doEncode(node.left,sb.append(0),s);
            sb.deleteCharAt(sb.length()-1);
            s = doEncode(node.right,sb.append(1),s);
            sb.deleteCharAt(sb.length()-1);
        }else{
            node.code = sb.toString();
            while(s.contains(String.valueOf(node.ch))){
                s = s.replace(String.valueOf(node.ch), node.code);
            }
        }
        return s;
    }

    public String encode(){
        return code;
    }

    public String decode(String code){
        Node node = root;
        StringBuilder sb = new StringBuilder();
        char[] charArray = code.toCharArray();
        for (int i = 0; i < charArray.length; i++) {
            if(charArray[i] == '0'){
                node = node.left;
            }else {
                node = node.right;
            }
            if(node.isLeaf()){
                sb.append(node.ch);
                node = root;
            }
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        HuffmanTree tree = new HuffmanTree("aabcccccc");
        String encode = tree.encode();
        System.out.println(encode);
        String decode = tree.decode(encode);
        System.out.println(decode);
    }
}
  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林小果呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值