算法系列之赫夫曼编码实战一数据压缩数据解压

Posted Roninaxious

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法系列之赫夫曼编码实战一数据压缩数据解压相关的知识,希望对你有一定的参考价值。


1.何谓赫夫曼编码?

哈夫曼编码是一种编码方式,哈夫曼编码是可变字长编码的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)。


有关哈夫曼树的相关文章

从计算机二进制认识哈夫曼编码【等长编码、变长编码】https://blog.csdn.net/Kevinnsm/article/details/120584624?spm=1001.2014.3001.5501
算法系列之赫夫曼树的精解【构造流程及原理分析】https://blog.csdn.net/Kevinnsm/article/details/120584098?spm=1001.2014.3001.5501

2.赫夫曼数据压缩

给定一串字符串,将其进行赫夫曼压缩

1.遍历字符串,将字符作为key,出现的频率作为value存储到map集合中;然后遍历该map集合,将其封装到EncryNode对象中,并将其加入到list集合中。
2.根据List集合构建哈夫曼树
3.根据哈夫曼树生成哈夫曼编码表
4.将十进制字符串,转换为二进制整数


package com.zsh.algorithm.tree;

import java.io.*;
import java.util.*;

/**
 * @author:Ronin
 * @since:2021/9/29
 * @email:1817937322@qq.com
 */

public class HuffmanEncrypt {

    //用来存储转换二进制字符串集合
    private static StringBuilder stringBuilder = new StringBuilder();
    //哈夫曼编码表
    private static Map<Byte, String> huffmanCodesMap = new HashMap<>();

//---------------------------------------以下是压缩相关函数------------------------------------------//
    /**
     * 赫夫曼编码封装方法
     *
     * @param bytes
     * @return
     */
    public static byte[] huffmanZip(byte[] bytes) {
        //1.计算字符串中每个字符出现的次数,并字符、次数封装到EncryptNode结点中,将其加入到list集合中
        List<EncryptNode> encryptNodeList = calByteNums(bytes);
        //outputList(encryptNodeList);
        //2.构建哈夫曼树
        EncryptNode root = buildHuffmanTree(encryptNodeList);
        // preErgodic(root);
        //3.生成赫夫曼编码表
        Map<Byte, String> huffmanCodes = getHuffmanCodes(root);
        //System.out.println(huffmanCodes);
        //4.转换为赫夫曼编码字节表
        return zip(bytes, huffmanCodes);
    }

    /**
     * 计算字符串中的每个字符的长度
     *
     * @param bytes 待计算的字符串
     * @return 返回list集合
     */
    public static List<EncryptNode> calByteNums(byte[] bytes) {
        Map<Byte, Integer> map = new HashMap<>();
        byte[] charArray = bytes;
        for (byte c : charArray) {
            if (map.containsKey(c)) {
                int num = map.get(c);
                map.replace(c, num, num + 1);
            } else {
                map.put(c, 1);
            }
        }
        List<EncryptNode> list = new ArrayList<>();
        for (Map.Entry<Byte, Integer> entry : map.entrySet()) {
            Byte key = entry.getKey();
            Integer value = entry.getValue();
            list.add(new EncryptNode(key, value));
        }
        return list;
    }

    /**
     * 构建赫夫曼树
     *
     * @param list 权重集合
     * @return 返回root节点
     */
    public static EncryptNode buildHuffmanTree(List<EncryptNode> list) {
        while (list.size() > 1) {
            Collections.sort(list);
            EncryptNode leftNode = list.remove(0);
            EncryptNode rightNode = list.remove(0);
            EncryptNode parentNode = new EncryptNode(null, leftNode.value + rightNode.value);
            parentNode.left = leftNode;
            parentNode.right = rightNode;
            list.add(parentNode);
        }
        return list.remove(0);
    }

    /**
     * 重载getHuffmanCodes(EncryptNode node, String path,  StringBuilder stringBuilder)
     *
     * @param root 根节点
     * @return 哈夫曼编码map集合
     */
    public static Map<Byte, String> getHuffmanCodes(EncryptNode root) {
        if (root == null) {
            return null;
        } else {
            getHuffmanCodes(root.left, "0", stringBuilder);
            getHuffmanCodes(root.right, "1", stringBuilder);
        }
        return huffmanCodesMap;
    }

    /**
     * 赫夫曼编码
     *
     * @param node          结点
     * @param path          1代表右结点,0代表左结点路径
     * @param stringBuilder 用于path拼接
     */
    public static void getHuffmanCodes(EncryptNode node, String path, StringBuilder stringBuilder) {
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        stringBuilder2.append(path);
        if (node != null) {
            if (node.c == null) {
                getHuffmanCodes(node.left, "0", stringBuilder2);
                getHuffmanCodes(node.right, "1", stringBuilder2);
            } else {
                huffmanCodesMap.put(node.c, stringBuilder2.toString());
            }
        }
    }

    /**
     * @param bytes 待编码字节数组
     * @param entry 赫夫曼编码集合
     * @return 返回编码后的byte数组,将十进制字符串111001100101转换为二进制byte数组
     */
    public static byte[] zip(byte[] bytes, Map<Byte, String> entry) {
        StringBuilder stringBuilder = new StringBuilder();
        for (byte ch : bytes) {
            stringBuilder.append(entry.get(ch));
        }
        //System.out.println("stringBuilder=" + stringBuilder.toString());
        int length = stringBuilder.length();
        int len = (length + 7) / 8;
        byte[] huffmanCodeBytes = new byte[len];
        int index = 0;
        for (int k = 0; k < length; k += 8) {
            String str;

            if ((k + 8) > length) {
                str = stringBuilder.substring(k);
            } else {
                str = stringBuilder.substring(k, k + 8);
            }
            huffmanCodeBytes[index] = (byte) Integer.parseInt(str, 2);
            index++;
        }
        return huffmanCodeBytes;
    }

    /**
     * 计算赫夫曼压缩率
     *
     * @param original 未压缩前的字节数组
     * @param newByte  压缩后的字节数组
     * @return 压缩率
     */
    public static String calHuffmanZipRatio(byte[] original, byte[] newByte) {
        double zipRatio = (double) (original.length - newByte.length) / original.length * 100;
        String s = String.format("%.2f", zipRatio);
        return s + "%";
    }

    //---------------------------------以上是压缩相关函数--------------------------------------------------------//


    //先序遍历赫夫曼树
    public static void preErgodic(EncryptNode cur) {
        if (cur == null) {
            return;
        }
        System.out.println(cur);
        preErgodic(cur.left);
        preErgodic(cur.right);
    }

    //打印list集合
    public static <T> void outputList(List<T> list) {
        for (T t : list) {
            System.out.println(t);
        }
    }
}

/**
 * 实现Comparable接口,便于使用Collections.sort排序
 */
class EncryptNode implements Comparable<EncryptNode> {
    public int value;   //出现的次数
    public Byte c;      //字符
    public EncryptNode left;
    public EncryptNode right;

    public EncryptNode(int value) {
        this.value = value;
    }

    public EncryptNode(Byte c, int value) {
        this.value = value;
        this.c = c;
    }

    @Override
    public int compareTo(EncryptNode o) {
        return this.value - o.value;   //升序
    }

    @Override
    public String toString() {
        return "EncryptNode{" +
                "value=" + value +
                ", c=" + c +
                '}';
    }
}

3.赫夫曼数据解压

1.将字节转换位二进制字符串,这涉及到高位补齐,地位截取的情况。
2.将赫夫曼编码表中的key和value进行置换,因为原赫夫曼编码表的key是字符,value是二进制字节;而我们解压时要扫描一大串二进制,需要根据部分二进制找到赫夫曼编码表中的字符,所以需要进行置换。编码我们根据二进制获取字符

 private static byte[] decode(Map<Byte, String> huffmanCodesMap, byte[] bytes) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int k = 0; k < bytes.length; k++) {
            boolean flag = (k == bytes.length - 1);
            stringBuilder.append(byteToBinaryString(!flag, bytes[k]));
        }
        //将map中的key和value置换
        Map<String, Byte> reverseMap = new HashMap<>();
        for (Map.Entry<Byte, String> map : huffmanCodesMap.entrySet()) {
            reverseMap.put(map.getValue(), map.getKey());
        }
        //根据赫夫曼编码进行字符串的匹配
        /**
         *例如以1001010101100000111为例
         *将变量i指向第一位1,然后变量count进行移动
         */
        List<Byte> list = new ArrayList<>();
        for (int i = 0; i < stringBuilder.length(); ) {
            int count = 1;
            Byte b = null;
            boolean flag = true;
            while (flag) {
                String key = stringBuilder.substring(i, i + count);
                b = reverseMap.get(key);
                if (b == null) {
                    count++;
                } else {
                    flag = false;
                }
            }
            list.add(b);
            i += count;
        }
        //将list集合中的结果数据封装到字节数组中
        byte[] result = new byte[list.size()];
        for (int k = 0; k < result.length; k++) {
            result[k] = list.get(k);
        }
        return result;
    }

    /**
     * @param flag 如果为true, 表示需要进行高位补齐;如果是false,表示不需要进行高位补齐-->因为最后一位不一定刚好是满足八位
     * @param be   这是将转换为二进制字符串的字节
     * @return 返回二进制字符串
     */
    private static String byteToBinaryString(boolean flag, byte be) {
        int beInt = be;
        //进行if判断是因为如果是最为一个字符的二进制不满足8位,这是和压缩时对应的,所以不需要进行高位补齐,低位截取。
        if (flag) {
            beInt |= 256;                                   //按位与  , 解决正数的高位补齐的问题
        }
        String s = Integer.toBinaryString(beInt);           //补码,正数需要高位补齐,负数需要截取
        if (flag) {
            return s.substring(s.length() - 8);
        } else {
            return s;
        }
    }

4.全代码

package com.zsh.algorithm.tree;

import java.io.*;
import java.util.*;

/**
 * @author:Ronin
 * @since:2021/9/29
 * @email:1817937322@qq.com
 */

public class HuffmanEncrypt {
    public static void main(String[] args) {

    }

    //用来存储转换二进制字符串集合
    private static StringBuilder stringBuilder = new StringBuilder();
    //哈夫曼编码表
    private static Map<Byte, String> huffmanCodesMap = new HashMap<>();

//---------------------------------------以下是压缩相关函数------------------------------------------//
    /**
     * 赫夫曼编码封装方法
     *
     * @param bytes
     * @return
     */
    public static byte[] huffmanZip(byte[] bytes) {
        //1.计算字符串中每个字符出现的次数,并字符、次数封装到EncryptNode结点中,将其加入到list集合中
        List<EncryptNode> encryptNodeList = calByteNums(bytes);
        //outputList(encryptNodeList);
        //2.构建哈夫曼树
        EncryptNode root = buildHuffmanTree(encryptNodeList);
        // preErgodic(root);
        //3.生成赫夫曼编码表
        Map<Byte, String> huffmanCodes = getHuffmanCodes(root);
        //System.out.println(huffmanCodes);
        //4.转换为赫夫曼编码字节表
        return zip(bytes, huffmanCodes);
    }

    /**
     * 计算字符串中的每个字符的长度
     *
     * @param bytes 待计算的字符串
     * @return 返回list集合
     */
    public static List<EncryptNode> calByteNums(byte[] bytes) {
        Map<Byte, Integer> map = new HashMap<>();
        byte[] charArray = bytes;
        for (byte c : charArray) {
            if (map.containsKey(c)) {
                int num = map.get(c);
                map.replace(c, num, num + 1);
            } else {
                map.put(c, 1);
            }
        }
        List<EncryptNode> list = new ArrayList<>();
        for (Map.Entry<Byte, Integer> entry : map.entrySet()) {
            Byte key = entry.getKey();
            Integer value = entry.getValue();
            list.add(new EncryptNode(key, value));
        }
        return list;
    }

    /**
     * 构建赫夫曼树
     *
     * @param list 权重集合
     * @return 返回root节点
     */
    public static EncryptNode build

以上是关于算法系列之赫夫曼编码实战一数据压缩数据解压的主要内容,如果未能解决你的问题,请参考以下文章

基于哈夫曼树的数据压缩算法

图像压缩基于matlab余弦变换及霍夫曼编码jpeg压缩和解压含Matlab源码 2086期

图像压缩基于matlab余弦变换及霍夫曼编码jpeg压缩和解压含Matlab源码 2086期

数据结构与算法-赫夫曼编码

哈夫曼编码压缩与解压思路分析与Java代码实现

C# 霍夫曼二叉树压缩算法实现