霍夫曼编码的压缩与解压

霍夫曼编码

百度定义霍夫曼编码(Huffman Coding),又称哈夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)
在了解赫夫曼编码前先了解一下,还有要先学会构建 传送门霍夫曼树
定长编码:太长
变长编码:多义性
哈夫曼编码:牛!!(刚学完霍夫曼树还不知道干啥,直到霍夫曼编码)
一,压缩流程
二,压缩全代码
三,解压流程
四,解压全代码
五,遇到的问题!!!!
赫夫曼编码压缩流程(整个流程需要传入byte数组,传出byte数组)

  1. 给定字符串
    在这里插入图片描述

  2. 得到每一个字母对应的ASCII码值
    在这里插入图片描述
    在这里插入图片描述

  3. 统计每个数字出现的次数(即该字母出现的次数)
    在这里插入图片描述
    在这里插入图片描述

  4. 这里将字母和出现的次数,分别分配给Node节点的data属性(数据)和weight属性(权重),并将其排序,得到权值有序的Node节点序列
    在这里插入图片描述
    在这里插入图片描述

  5. 然后对该集合操作成哈夫曼树
    在这里插入图片描述
    在这里插入图片描述

  6. 将赫夫曼树中的叶子节点(即存在的节点)转化为对应的路径形式(路径向左为0,向右为1)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  7. 将文本用二进制字符串表示
    在这里插入图片描述
    在这里插入图片描述

  8. 将二进制字符串其压缩成字节数组
    在这里插入图片描述
    在这里插入图片描述

  9. 将子方法整合
    在这里插入图片描述
    在这里插入图片描述

压缩全代码

public class HuffmanCoding {
    public static void main(String[] args) {
        String conText = "i like like like java do you like a java";
        zip(conText);
    }

    //将子方法整合起来
    public static void zip(String conText) {
        Node huffmanRoot = getHuffmanRoot(conText);//获得哈夫曼根节点
        String huffmanCoding = contextHuffmanCoding(huffmanRoot, conText);//根据哈夫曼根节点获得哈夫曼编码
        Byte[] bytes = zipHuffman(huffmanCoding);//将哈夫曼编码压缩成哈夫曼byte数组
        System.out.println("哈夫曼字节数组:" + Arrays.toString(bytes));
    }
    //得到哈夫曼树的根节点
    public static Node getHuffmanRoot(String conText) {
        byte[] bytes = conText.getBytes();
        Map<Byte, Integer> map = new HashMap<>();
        for (byte aByte : bytes) {
            Integer count = map.get(aByte);
            if (count == null) {
                map.put(aByte, 1);
            } else {
                map.put(aByte, count + 1);
            }
        }
        ArrayList<Node> nodes = new ArrayList<>();
        for (Map.Entry<Byte, Integer> entry : map.entrySet()) {
            nodes.add(new Node(entry.getKey(), entry.getValue()));
        }
        //得到权值有序的node集合
        while (nodes.size() > 1) {
            Collections.sort(nodes);//每一次while循环完都是无序的,所以要排序
            Node leftNode = nodes.get(0);
            Node ringhtNode = nodes.get(1);
            //过度节点,详情是关于哈夫曼树的构建
            Node parent = new Node(null, leftNode.weight + ringhtNode.weight);
            nodes.add(parent);//将父节点加入到集合中
            parent.left = leftNode;//设置父节点的左孩子
            parent.right = ringhtNode;//设置父节点的右孩子
            nodes.remove(leftNode);//移除左孩子
            nodes.remove(ringhtNode);//移除右孩子
        }
        return nodes.get(0);//最后集合里剩下一个元素,该元素就是该huffman树的根节点
    }
    //6. 得到赫夫曼编码
    static HashMap<Byte, String> map = new HashMap<>();//该map存放数字与对应的路径

    public static void getRoad(Node node, String code, StringBuilder stringBuilder) {
        StringBuilder stringBuilder1 = new StringBuilder(stringBuilder);
        stringBuilder1.append(code);
        if (node != null) {
            if (node.data == null) {
                //向左递归
                getRoad(node.left, "0", stringBuilder1);
                //向右递归
                getRoad(node.right, "1", stringBuilder1);
            } else {
                map.put(node.data, stringBuilder1.toString());
            }
        }
    }
    //将文字用赫夫曼编码表示
    public static String contextHuffmanCoding(Node huffmanRoot, String conText) {
        //得到哈夫曼树的根节点
        //得到每个叶子节点的路径,向左为0,向右为1,(自己可以去重载)
        getRoad(huffmanRoot, "", new StringBuilder());
        byte[] bytes = conText.getBytes();//得到文字的字节数组
        StringBuilder stringBuilder = new StringBuilder();
        for (byte aByte : bytes) {
            stringBuilder.append(map.get(aByte));
        }
        //System.out.println(stringBuilder);
        return stringBuilder.toString();
    }
    //将赫夫曼编码压缩成字节数组
    public static Byte[] zipHuffman(String huffmanCoding) {
        int length = huffmanCoding.length();//133
        int byteLength = (length + 7) / 8;//确定我的byte数组的大小
        Byte[] bytes = new Byte[byteLength];
        int index = 0;//bytes数组的下标
        for (int i = 0; i < length; i = i + 8) {
            String substring;
            if (i + 8 > length) {
                substring = huffmanCoding.substring(i);
            } else {
                substring = huffmanCoding.substring(i, i + 8);
            }
            //将二进制转化为byte
            bytes[index] = (byte) Integer.parseInt(substring, 2);
            index++;
        }
        return bytes;
    }
}

赫夫曼编码解压流程

  1. 将byte数组转化为二进制字符串
    在这里插入图片描述
    在这里插入图片描述
  2. 将二进制字符串根据map映射转出文本
    在这里插入图片描述
    在这里插入图片描述

解压全代码:

public class HuffmanCodingUnpack {
    public static void main(String[] args) {
        String conText = "i like like like java do you like a java like do you java php c ddddddd";
        Byte[] zip = zip(conText);
        String toBinary = byteToBinary(zip);
        getContext(toBinary);
    }

    //根据字节数组转化成二级制字符串
    public static String byteToBinary(Byte[] bytes) {
        StringBuilder stringBuilder = new StringBuilder();
        int index = 0;
        /**
         * 这里有三种情况,
         * 一,负数:借助Integer的方法转二进制的方法,截取后8位
         * 二,正数:正数转成二进制直接为其二进制形式
         *        情况一:在中间的数,用该表达式:temp |= 256;将其变成9位,截取后八位
         *        情况二:在最后的数,直接转成二进制即可(不过这里有个怎末确定是最后的一组数,我这里用了统计)
         *                 如果最后存进来的二进制是01111001类似,映射到byte数组中是121,而121再解压出来是1111001,
         *                 会导致最后的几位错误,所以这里大家需要再解决这个问题
         */
        for (Byte aByte : bytes) {
            String substring = "";
            if (aByte < 0) {
                String s = Integer.toBinaryString(aByte);
                substring = s.substring(s.length() - 8);
                stringBuilder.append(substring);
                index++;
            } else {
                index++;
                if (index < bytes.length) {
                    int temp = aByte;
                    temp |= 256;
                    String s = Integer.toBinaryString(temp);
                    substring = s.substring(s.length() - 8);
                    stringBuilder.append(substring);
                } else {
                    String s = map.get(null);
                    stringBuilder.append(s);
                }
            }
        }
        System.out.println(stringBuilder);
        return stringBuilder.toString();
    }
    //将二进制字符串根据map映射转出文本
    public static void getContext(String binaryStr) {
        //得到HuffmanCodingZip类内的映射关系,并反转放入新的map集合
        HashMap<Byte, String> map = HuffmanCodingZip.map;
        HashMap<String, Byte> newMap = new HashMap<>();
        for (Map.Entry<Byte, String> entry : map.entrySet()) {
            newMap.put(entry.getValue(), entry.getKey());
        }
        StringBuilder stringBuilder = new StringBuilder();//声明新的可拼接字符串
        //遍历新的map
        for (Map.Entry<String, Byte> entry : newMap.entrySet()) {
            System.out.println(entry.getKey() + "==>" + entry.getValue());
        }
        int index = 0;
        for (int i = index; i <= binaryStr.length(); i++) {
            String subStr = binaryStr.substring(index, i);
            if (newMap.containsKey(subStr)) {
                byte b = newMap.get(subStr);
                stringBuilder.append((char) b);
                index = i ;
            }
        }
        System.out.println("字符串"+stringBuilder);
    }
}

对于遇到的一些问题:
对于解压过程中,对于二进制转换成byte数这里有两种情况,

  1. 负数:借助Integer的方法转二进制的方法,截取后8位
  2. 正数:正数转成二进制直接为其二进制形式
    情况一:在中间的数,用该表达式:temp |= 256;将其变成9位,截取后八位
    情况二:(刚开始的想法,后来该想法错误)在最后的数,直接转成二进制即可
    (此想法错误的原因)如果最后存进来的二进制是01111001类似,映射到byte数组中是121,而121再解压出来是1111001,会导致最后的几位错误,所以这里大家需要再解决这个问题

解决:小编自己想了一下,既然最后的情况如此难以捉摸,不如就借着(map)这条船,将最后的难以捉摸的二进制码直接存入map集合,如:map.put(null,substring);然后最后直接从map中获取,尽管消耗了一定空间,但是在另一种程度上减少了,对于二进制码的判断(再者说,最后的一节二进制码对于整个文本来说,如同大海一粟,)
希望大家有其它方法讨论并提供解决方案;(推荐大家去学习韩顺平的数据结构)

  • 2
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值