压缩(Compression)/霍夫曼编码原理及实现(Java)

  不同的文件各种信息出现的频率不同;
  一种单一的编码方式可能只对某些文件而言是最优解;
  霍夫曼编码:根据文件中各种信息出现的频率进行变长编码。
  以英文文本进行举例说明(汉字、图像中的bit可以类推)。
霍夫曼编码
  根据出现的频率对信息进行编码,如图例所示。
在这里插入图片描述


思路
  给定一个字符串,计算每个符号出现的频率,从小到大排列;
  使用哈夫曼编码的方式生成编码map和解码trie;
  使用编码map将字符串压缩编码;
  将编码后的bit和解码trie一起传输;
  使用解码trie将字符串解码。


java代码
  Counter.java

import java.util.*;

public class Counter {
    /**
     * 给定一个字符串,计算每个符号出现的频率,从小到大排列;
     */
    private HashMap<Character, Integer> res;
    private List<Pair> charPairs;
    private String s;

    Counter(String s) {
        res = new HashMap<>();
        this.s = s;
        count();
    }

    private void count() {
        for (int i = 0; i < s.length(); i ++) {
            char curChar = s.charAt(i);
            if (res.containsKey(curChar)) {
                res.put(curChar, res.get(curChar) + 1);
            }
            res.putIfAbsent(curChar, 1);
        }
        charPairs = sortByValue(res);
    }

    public List<Pair> getCharPairs() {
        return charPairs;
    }

    private class selfComparator implements Comparator<Pair> {
        /* 自定义Comparator
         */
        @Override
        public int compare(Pair o1, Pair o2) {
            if (o1.getTimes() < o2.getTimes()) {
                return -1;
            } else if (o1.getTimes() > o2.getTimes()) {
                return 1;
            }
            return 0;
        }
    }

    private List<Pair> sortByValue(HashMap<Character, Integer> input) {
        Set entyrSet = input.entrySet();
        List<Pair> res = new ArrayList<>();
        for (Object e : entyrSet) {
            Map.Entry ee = (Map.Entry) e;
            Pair temp = new Pair((char)ee.getKey(), (Integer) ee.getValue());
            res.add(temp);
        }
        Collections.sort(res, new selfComparator());
        return res;
    }


}

  Huffman.java

import java.util.*;

public class Huffman {
    /**
     * 使用Huffman编码对输入的信息进行编码
     * 生成编码Map和解码Trie
     */
    private List<TrieNode> input;
    private TrieNode root;
    private HashMap<Character, String> map;

    Huffman(List<Pair> input) {
        this.input = new ArrayList<>();
        for (Pair p : input) {
            TrieNode t = new TrieNode(p);
            this.input.add(t);
        }
        getTrie();
        getMap();
    }

    public HashMap<Character, String> getHufMap() {
        return map;
    }

    public class TrieNode {
        /**
         * 将input的Pair转换为Trie中的节点
         * 初始时每个都存的一个节点
         * 然后将最小的两个节点合并
         * 更新数组
         */
        private Pair p;
        private TrieNode left;
        private TrieNode right;

        TrieNode(Pair p) {
            this.p = p;
            left = null;
            right = null;
        }
    }

    private class selfComparator implements Comparator<TrieNode> {
        /* 自定义Comparator
         */
        @Override
        public int compare(TrieNode o1, TrieNode o2) {
            if (o1.p.getTimes() < o2.p.getTimes()) {
                return -1;
            } else if (o1.p.getTimes() > o2.p.getTimes()) {
                return 1;
            }
            return 0;
        }
    }

    public void getTrie() {
        while (input.size() != 1) {
            TrieNode first = input.get(0);
            TrieNode second = input.get(1);
            int totalTimes = first.p.getTimes() + second.p.getTimes();
            TrieNode mergeNode = new TrieNode(new Pair('\0', totalTimes));
            mergeNode.left = first;
            mergeNode.right = second;
            deleteFirstTwo();
            input.add(mergeNode);
            Collections.sort(input, new selfComparator());
        }
        root = input.get(0);
    }

    /**
     * 对Trie进行DFS,构造编码map
     * 左1右0
     */
    public void getMap() {
        map = new HashMap<>();
        getMapHelper(root, "");
    }

    private void getMapHelper(TrieNode curNode, String prevCode) {
        if (isLeaf(curNode)) {
            map.put(curNode.p.getChar(), prevCode);
            return;
        }
        getMapHelper(curNode.left, prevCode + "1");
        getMapHelper(curNode.right, prevCode + "0");
    }

    /**
     * 将输入的String编码为二进制格式
     * @param inputString 待编码字符串
     * @return 编码后的二进制String
     */
    public String enCodeString (String inputString) {
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < inputString.length(); i++) {
            char curChar = inputString.charAt(i);
            res.append(map.get(curChar));
        }
        return res.toString();
    }

    /**
     * 将编码后的二进制信息解码为String
     * @param code 哈夫曼编码后的二进制信息
     * @return 解码后的String
     */
    public String deCodeToString (String code) {
        return deCodeHelper(root, code, "");
    }

    private String deCodeHelper(TrieNode curNode, String code, String prev) {
        if (code.equals("")) {
            prev += curNode.p.getChar();
            return prev;
        } else {
            if (isLeaf(curNode)) {
                prev += curNode.p.getChar();
                return deCodeHelper(root, code, prev);
            } else {
                char curChar = code.charAt(0);
                if (curChar == '1') {
                    return deCodeHelper(curNode.left, code.substring(1), prev);
                } else {
                    return deCodeHelper(curNode.right, code.substring(1), prev);
                }
            }
        }
    }

    /**
     * 判断当前节点是否为叶子节点
     * @param curNode
     * @return
     */
    private boolean isLeaf(TrieNode curNode) {
        return curNode.left == null &&
                curNode.right == null;
    }

    /**
     * 删除input的前两个节点
     */
    private void deleteFirstTwo() {
        input.remove(0);
        input.remove(0);
    }
}

  Pair.java

public class Pair {
    /**
     * char和它出现的频次
     */
    private char c;
    private int times;

    Pair(char c, int times) {
        this.c = c;
        this.times = times;
    }

    public char getChar() {
        return c;
    }

    public int getTimes() {
        return times;
    }
}

  Test.java

public class Test {

    /**
     * 对压缩和解压缩环节进行测试
     */
    public static void main(String[] args) {
        // 对Counter进行测试
        String testString = "China will win this competition!";
        Counter t = new Counter(testString);
        System.out.println("编码前:" + testString);
//        for (Pair p : t.getCharPairs()) {
//            System.out.println(p.getChar() + " " + p.getTimes());
//        }

        // 对哈夫曼编码进行测试
        Huffman huf = new Huffman(t.getCharPairs());
        //System.out.println(huf.getHufMap());

        // 对String进行编码
        String code = huf.enCodeString(testString);
        System.out.println("编码后:" + code);

        // 对解码过程进行测试,同时测试前面的所有功能
        String originMess = huf.deCodeToString(code);
        System.out.println("解码后:" + originMess);
    }

}



To be a sailor of the world bound for all ports.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

carpe~diem

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

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

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

打赏作者

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

抵扣说明:

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

余额充值