霍夫曼编码

233 阅读3分钟

霍夫曼编码(Huffman coding)是一种常用的数据压缩算法,它是由David A. Huffman在1952年提出的。该算法通过使用变长编码来表示不同的字符,使出现频率较高的字符拥有较短的编码,而出现频率较低的字符拥有较长的编码,从而实现数据的高效压缩。

以下是霍夫曼编码的基本原理和步骤:

  1. 统计字符频率:首先,需要统计待压缩数据中每个字符的出现频率。这个频率信息将用于构建霍夫曼树。
  2. 构建霍夫曼树:使用字符频率信息构建霍夫曼树。霍夫曼树是一种二叉树,其中字符频率较高的字符位于树的较低层,而频率较低的字符位于较高层。构建过程通常是通过不断合并两个频率最低的节点来进行的,直到只剩下一个根节点。这个过程保证了较常见的字符拥有较短的编码,而较不常见的字符拥有较长的编码。
  3. 分配编码:从霍夫曼树的根节点开始,向左走表示0,向右走表示1,从根节点到每个叶子节点的路径即为字符的编码。这些编码是变长的,这是霍夫曼编码的一个关键特点,因为不同字符的编码长度不同。
  4. 压缩数据:使用生成的霍夫曼编码替换原始数据中的字符,将数据压缩为较短的比特流。这个过程可以显著减小数据的大小,特别是对于包含大量重复字符的数据。
  5. 解压数据:要解压数据,需要使用相同的霍夫曼树来将编码重新映射为原始字符。

霍夫曼编码的优势在于它能够根据字符的频率动态地分配编码,以使常见字符的编码较短,从而实现高效的数据压缩。这种编码方式在无损压缩领域广泛应用,如在图像、音频和文本数据的压缩中,例如JPEG图像压缩、MP3音频压缩以及ZIP文件压缩。

需要注意的是,霍夫曼编码是一种无损压缩算法,这意味着压缩和解压后的数据是精确一致的,不会丢失信息。

public class HuffmanTree {


    static class Node {
        Character ch; //字符
        int freq;//频次
        Node left;
        Node right;
        String code; //编码


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

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

        public Node(Character ch, int freq, Node left, Node right, String code) {
            this.ch = ch;
            this.freq = freq;
            this.left = left;
            this.right = right;
            this.code = code;
        }


        public int freq() {
            return freq;
        }

        boolean isLeaf() {
            return left == null;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "ch=" + ch +
                    ", freq=" + freq +
                    '}';
        }
    }

    String str;
    Map<Character, Node> map = new HashMap<>();
    Node root;

    public HuffmanTree(String str) {
        this.str = str;
        char[] chars = str.toCharArray();

        //功能1:统计频率
        for (char c : chars) {
//            if (!map.containsKey(c)) {
//                map.put(c, new Node(c));
//            }
//            Node node = map.get(c);
//            node.freq++;
            Node node = map.computeIfAbsent(c, Node::new);
            node.freq++;
        }
        //功能2,构造树
        PriorityQueue<Node> queue = new PriorityQueue<>(
                Comparator.comparingInt(Node::freq)
        );
        queue.addAll(map.values());
        while (queue.size() >= 2) {
            Node x = queue.poll();
            Node y = queue.poll();
            int freq = x.freq + y.freq;
            queue.offer(new Node(freq, x, y));
        }
        root = queue.poll();
        System.out.println(root);
        //功能3:计算每个字符的编码,功能4:字符串编码后占用bits
        int sum = dfs(root, new StringBuilder());
        for (Node node : map.values()) {
            System.out.println(node + " " + node.code);
        }
        System.out.println("总共会占用 bits:" + sum);
    }

    private int dfs(Node node, StringBuilder code) {
        int sum = 0;
        if (node.isLeaf()) {
            //找到编码
            node.code = code.toString();
            sum = node.freq * code.length();
        } else {
            sum += dfs(node.left, code.append("0"));
            sum += dfs(node.right, code.append("1"));
        }
        if (code.length() > 0) {
            code.deleteCharAt(code.length() - 1);
        }
        return sum;
    }

    //编码
    public String encode() { //000101111111
        char[] chars = str.toCharArray();
        StringBuilder sb = new StringBuilder();
        for (char c : chars) {
            sb.append(map.get(c).code);
        }
        return sb.toString();
    }

    //解码
    public String decode(String str) {
        char[] chars = str.toCharArray();
        int i = 0;
        StringBuilder sb = new StringBuilder();
        Node node = root;
        while (i < chars.length) {
            if(!node.isLeaf()){ //非叶子
                if(chars[i] == '0'){ //向左走
                    node = node.left;
                } else if (chars[i] == '1') {//向右走
                    node = node.right;
                }
                i++;
            }
            if(node.isLeaf()){
                sb.append(node.ch);
                node = root;
            }

        }
        return sb.toString();
    }

    /*
            Huffman 树的构建过程
            1.将统计了出现频率的字符,放入优先级队列

            2.每次出队两个频次最低的元素,给它俩找个爹
            3.把爹重新放入队列,重复2~3
            4.当队列只剩一个元素时,Huffman树构建完成
         */
    public static void main(String[] args) {
        HuffmanTree tree = new HuffmanTree("abbccccccc");
        String encode = tree.encode();
        System.out.println(encode);
        System.out.println(tree.decode(encode));

    }


}