哈夫曼编码的实现

哈夫曼编码

在说哈夫曼编码之前,先要讲清楚编码集是什么东西。相信写过代码的人,一定听说过ASCII、 UTF-8、GBK 这些编码集。这些编码集合,本质上都是一个二进制和字符之间映射关系,拿最简单的 ASCII 来说吧,使用 0x30 代表字符 0 ,0x31 代表字符 1,当打印设备看到 0x30 后,就打印出字符 0 了。ASCII 最初是由美国弄出来,他们使用的英文,所以只考虑了英文中的字符,那还有很多字符,比如,阿拉伯文字、俄文这些文字怎么表示,于是就有了 UTF-8 这些。

讲了半天,哈夫曼编码和字符集有什么关系呢?大家都知道在计算机刚诞生的时候,其内存是非常小的,所以希望能设计出一个字符集,这个字符集提高单位内存保存数据信息量。以 ASCII 码为例,它使用 1 个字节保存来保存,如果一封 e-mail 有 150 个字符,则需要 150 个字节来存储。那么有没有办法减少一些呢?大家仔细观察,英文的字符,有的使用比较多,有的比较少,能不能使用比较短的比特为表示比较常用的字符呢?比如,a 比 z 使用的比较多,我们可以使用两个比特位来表示,z 可以使用三个表示。这样的话,在越长的文本中,使用这种方式保存数据,是不是越能节省内存。

哈夫曼编码就是生成这样自定义字符集的算法。

上代码

哈弗树:

    /**
     输入一个字符串,计算出各个字符出现的次数,出现的次数作为 Node 中的权重。
     然后在将 Node 放入到小根堆中。
    */
    public static PriorityQueue<Node> getQueue(String input){
        Map<Character , Integer> cha = new HashMap<>();
        for (char c : input.toCharArray()) {
                cha.computeIfPresent(c , (key , oldValue)->{
                    return oldValue + 1;
                });
                cha.computeIfAbsent(c , x -> {
                    return 1;
                });
        }
        PriorityQueue<Node> nodes = new PriorityQueue<>(cha.size());
        cha.forEach((key , value) -> {
            nodes.add(new Node(key , null , null , value));
        });
        return nodes ;
    }
    /**
     根据小根堆,生成 Huffman 树。
    */
    public static Node huffmanTree(PriorityQueue<Node> queue){
        Node head = null ;
        Node first = null ;
        Node second = null ;
        Node h =  null ;
        while(!queue.isEmpty()){
            first = queue.poll();
            second = queue.poll();
            if(null == second){
                break ;
            }
            h = new Node(null , first , second , first.weight + second.weight);
            head = h ;
            queue.add(h);
        }
        return head ;
    }
    public static class Node implements Comparable<Node>{
        public Node left ;
        public Node right ;
        public int weight ;
        public Character c ;
        public Node(Character c , Node l , Node r , int w){
            left = l ;
            right = r ;
            weight = w ;
            this.c = c ;
        }

        @Override
        public int compareTo(Node o) {
            return this.weight - o.weight;
        }
    }   
    /**
     返回字符对应的 Huffman 编码
    */
        public static Map<Character , String> huffmanCode(Node head){
        Map<Character , String> rs = new HashMap<>();
        maxLevel = process2(head, "", 0, rs);
        return rs ;
    }
    /**
     递归的找到 Huffman 编码
    */
    public static int process2(Node head , String binaryStr , int value , Map<Character , String> map){
        if(head.c != null){
           map.put(head.c ,  binaryStr + value);
           int p = 1 + binaryStr.length();
           return p;
        }
        int p1 = process2(head.left ,  binaryStr + "1" , 1 , map);
        int p2 = process2(head.right ,  binaryStr + "0" , 0 , map);
        return Math.max(p1 , p2);
    }    

转码码和解码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Iterator;
import java.util.Map;
import java.util.BitSet;
public class HuffmanEncoder {
    private static Logger logger = LoggerFactory.getLogger(HuffmanEncoder.class);
    /**
      使用 BitSet 保存 Huffman 编码
    */
    public static Info encode(String input , Map<Character , String> mapping){
        BitSet bs = new BitSet();
        int idx = 0 ;
        for(char c : input.toCharArray()){
            String code = mapping.get(c);
            char[] charArray = code.toCharArray();
            for(int i = charArray.length - 1 ; i >= 0 ; i-- ){
                bs.set(idx++ , charArray[i] == '1');
            }
        }
        Iterator<Map.Entry<Character, String>> iterator = mapping.entrySet().iterator();
        // max 的作用是保存 Huffman 编码的最大长度,使用 max + 1 长度的连续为 1 的比特位作为结尾
        int max = 0 ;
        while (iterator.hasNext()) {
            Map.Entry<Character, String> next = iterator.next();
            if(max < next.getValue().length()){
                max = next.getValue().length();
            }
        }
        // 设置结尾字符
        for (int i = 0; i < max+1; i++) {
            bs.set(idx++ , true);
        }
        return new Info(max+1 , bs) ;
    }
    // 返回的信息体,max 是结束比特位的长度。
    // BitSet 是字符串转码后的比特数据
    public static class Info{
        public int max ;
        public BitSet bs ;
        public Info(int m , BitSet b){
            max = m ;
            bs = b ;
        }
    }
}

解码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.BitSet;
import java.util.Map;

public class HuffmanDecoder {
    private static Logger logger = LoggerFactory.getLogger(HuffmanDecoder.class);
    public static String decode(HuffmanEncoder.Info info, Map<Integer, Character> mapping){
        StringBuilder sb = new StringBuilder();
        int sum = 0 ;
        int i = 0 ;
        int j = 0 ;
        int end = 0 ;
        for (int k = 0; k < info.max; k++) {
            end += (1 << k);
        }
        while(i < info.bs.length()) {
            sum += (info.bs.get(i)? 1<<j : 0);
            j++;
            // j 表示了当前比特的位数,当 >= max -1 的时候,才能解码
            // 避免混淆,例如,111 和 011 ,当读完 11 后,第三个 1 没读取,此时转化为 10 进制为 3 ,正好和 011 相等,
            // 这种情况就出现问题,所以一定要判断一下是否大于 info.max -1 
            if(mapping.containsKey(sum) && j >= info.max-1){
                sb.append(mapping.get(sum));
                sum = 0 ;
                j = 0 ;
            }
            // 判断是否等于结束字符。
            if(sum == end){
                break ;
            }
            i++;
        }
        return sb.toString();
    }
}
  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值