赫夫曼编码与解码

代码实现

1.节点
package HuffmanCode;

public class huffmanNodes implements Comparable<huffmanNodes> {
    private Byte data; // 存数据 比如,a-->97
    private int weight; // 数据的权值,这里是字符出现的次数
    private huffmanNodes left; // 节点的左指针
    private huffmanNodes right; // 节点的右指针

    public huffmanNodes() {
    }

    public huffmanNodes(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }

    public Byte getData() {
        return data;
    }

    public void setData(Byte data) {
        this.data = data;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public huffmanNodes getLeft() {
        return left;
    }

    public void setLeft(huffmanNodes left) {
        this.left = left;
    }

    public huffmanNodes getRight() {
        return right;
    }

    public void setRight(huffmanNodes right) {
        this.right = right;
    }

    // 前序遍历
    public void PreOrder() {
        System.out.println(this);
        if (this.left != null) {
            this.left.PreOrder();
        }
        if (this.right != null) {
            this.right.PreOrder();
        }
    }

    @Override
    public String toString() {
        return "{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }

    @Override
    public int compareTo(huffmanNodes o) {
        // 升序排序
        return this.weight - o.weight;
    }
}
2.赫夫曼编码
package HuffmanCode;

import java.util.*;

/**
 * @program: test01
 * @description: 测试
 * @author: Zhou Jian
 * @create: 2020-07-27 16:09
 */
public class Input {
    // 定义一个Map<Byte,String>,存放赫夫曼编码表
    // 形式: a 97-01.........
    static Map<Byte, String> HashManCodes = new HashMap<>();
    // 定义StringBuilder,用于拼接路径,存放叶子结点的路径
    static StringBuilder stringBuilder = new StringBuilder();

    public static void main(String[] args) {
        System.out.println("===================================编码=========================================");
        /*
            1.压缩数据 ---> 00010001000000.................
         */
        // 定义一个要编码的字符串
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入字符串:");
        String str = sc.nextLine();
        // 将其放入字节数组中
        byte[] bytesArr = str.getBytes();
        System.out.println("编码之前,要发送的内容是:" + str + ",对应的字节数组是:" + new String(bytesArr) + ",长度为:" + bytesArr.length);

        // 得到赫夫曼编码的字符数组 00010001000000.................
        byte[] zip = huffmanZip(bytesArr);
        System.out.println("编码之后,要发送的内容是:" + Arrays.toString(zip) + ",长度为:" + zip.length);

        double d = (double) zip.length / (double) bytesArr.length;
        System.out.printf("压缩率大概是:%.2f", d); // 压缩率大概0.5
        System.out.println();

        System.out.println("===================================解码=========================================");

        /*
           2.解压数据
         */
        byte[] decode = decode(HashManCodes, zip);
        System.out.println("解码后的字节数组是:" + new String(decode));


    }

    /**
     * 使用一个方法,将所写的方法封装起来,便于调用
     * 数据压缩
     *
     * @param bytes 原始的字符串对应的字节数组
     * @return 经过赫夫曼编码处理后的字节数组(压缩后的数组)
     */
    public static byte[] huffmanZip(byte[] bytes) {
        // 处理字节数组
        List<huffmanNodes> nodes = getNodes(bytes);
        // 创建赫夫曼树
        huffmanNodes root = HuffManTreeBuild(nodes);
        // 生成对应的赫夫曼编码(每个字节的0101编码)
        Map<Byte, String> codes = getCodes(root);
        // 根据生成的赫夫曼编码,得到压缩后的赫夫曼编码字节数组
        byte[] zipArr = zip(bytes, codes);
        return zipArr;
    }

    /* 一.压缩数据 */

    /**
     * 1.统计每个字符出现的次数
     *
     * @param byteArr 存储字符的字符数组
     * @return 存储节点的列表
     */
    public static List<huffmanNodes> getNodes(byte[] byteArr) {
        // 存储节点的列表
        ArrayList<huffmanNodes> nodes = new ArrayList<>();
        // 遍历字符数组,统计次数 ---> 使用map存储
        HashMap<Byte, Integer> obj = new HashMap<>();
        // 循环遍历字符数组
        // b ---> 取出的字符
        for (byte b : byteArr) {
            // 统计每个字符出现的次数
            Integer counts = obj.get(b);
            // 如果没有出现过,则将其值置为1
            if (counts == null) {
                obj.put(b, 1);
            } else {    // 否则,自加1
                obj.put(b, counts + 1);
            }
        }
        // 将每个键值对转化为Node节点,并添加到集合中
        for (Map.Entry<Byte, Integer> key_Value : obj.entrySet()) {
            nodes.add(new huffmanNodes(key_Value.getKey(), key_Value.getValue()));
        }
        return nodes;
    }

    /**
     * 2.构造赫夫曼树
     *
     * @param m 要排序的集合
     * @return 根节点
     */
    public static huffmanNodes HuffManTreeBuild(List<huffmanNodes> m) {
        while (m.size() > 1) {
            // 升序排序
            Collections.sort(m);
            // 处理的过程是个循环的过程
            // 开始创建赫夫曼树
            // 从集合中找出权值最小的节点
            huffmanNodes leftNode = m.get(0);
            // 从集合中找到权值次小的节点
            huffmanNodes rightNode = m.get(1);
            // 找到之后,产生其父节点
            huffmanNodes parent = new huffmanNodes(null, leftNode.getWeight() + rightNode.getWeight());
            // 重构父子关系
            parent.setLeft(leftNode);
            parent.setRight(rightNode);
            // 从集合中删除已经处理过的节点
            m.remove(leftNode);
            m.remove(rightNode);
            // 将新的父节点添加到集合中
            m.add(parent);
        }
        return m.get(0);
    }

    /**
     * 3.为了调用方便,这里重载getCodes方法
     *
     * @param root 传入的要处理的节点
     * @return 赫夫曼编码表
     */
    public static Map<Byte, String> getCodes(huffmanNodes root) {
        if (root == null) {
            return null;
        }
        // 递归左子节点
        getCodes(root.getLeft(), "0", stringBuilder);
        // 递归右子节点
        getCodes(root.getRight(), "1", stringBuilder);
        return HashManCodes;
    }


    /**
     * 3.将传入的节点的所有叶子结点的赫夫曼编码得到,并存放到HashManCodes集合中
     * 例如:97-001 78-010 ...................
     *
     * @param node          传入的节点
     * @param path          路径,左子节点---->0,右子节点---->1
     * @param stringBuilder 拼接路径
     */
    public static void getCodes(huffmanNodes node, String path, StringBuilder stringBuilder) {
        // 构造StringBuilder,用于存放结点的路径
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        // 将路径添加到集合中
        stringBuilder2.append(path);
        // 生成节点的路径
        if (node != null) {
            if (node.getData() == null) { // 不是叶子结点(使用赫夫曼树生成的节点)
                // 左子节点递归
                getCodes(node.getLeft(), "0", stringBuilder2);
                // 右子节点递归
                getCodes(node.getRight(), "1", stringBuilder2);
            } else { // 是叶子结点,就直接添加到赫夫曼map中
                HashManCodes.put(node.getData(), stringBuilder2.toString());
            }
        }
    }

    /**
     * 4.编写一个方法,将字符串对应的byte[]数组,通过生成的赫夫曼编码表,返回一个处理过后的字节数组
     * 例如:010000000000001010000000000000000001..................
     *
     * @param bytes        原始的字符串对应的byte数组
     * @param huffmanCodes 生成的赫夫曼编码
     * @return 处理后的byte数组
     */
    public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
        // 1.利用赫夫曼编码表将byte数组转换为赫夫曼编码后对应的字符串
        StringBuilder stringBuilder = new StringBuilder();
        // 2.遍历byte数组
        for (byte b : bytes) {
            // 取出byte的每一个字符,并转换为对应的赫夫曼编码,添加到StringBuilder中
            stringBuilder.append(huffmanCodes.get(b));
        }
        System.out.println("原始数据的字节数组对应的赫夫曼编码为:" + stringBuilder.toString());
        // 3.将00110110010101101000011111110011转换为byte数组

        // 统计byte数组的长度(便于截取--->8位一组)
        int length;
        if (stringBuilder.length() % 8 == 0) {
            length = stringBuilder.length() / 8;
        } else {
            length = stringBuilder.length() / 8 + 1;
        }
        // 创建 存储压缩后的byte数组
        byte[] by = new byte[length];
        // 记录第几个byte
        int index = 0;
        // 步长为8(8位一组)
        for (int i = 0; i < stringBuilder.length(); i += 8) {
            // 存放8位0101编码,不够时,就存储剩余几位
            String strByte;
            // 防止越界
            if (i + 8 > stringBuilder.length()) {
                // 取出剩下几位,i-i+8位
                strByte = stringBuilder.substring(i);
            } else {
                // 每循环1次,从stringBuilder中取出8位,存放在strByte中
                strByte = stringBuilder.substring(i, i + 8);
            }
            // 将strByte转换为一个byte,存放到by中
            by[index] = (byte) Integer.parseInt(strByte, 2);
            index++;
        }
        return by;
    }

    /* 二.解压数据
       <1>将得到的字节数组[97, -22, -69, 60, -118, -57, 21, 56, 126, 105, -83, -54, -77, -13, 68, -50, -71, -112, 92,-21, 1]
          转化为赫夫曼编码对应的字符数组 -------> "00100000000010......................."
       <2>将得到的字符数组,通过查看赫夫曼编码表,得到原始的字符串 ---------> I love you
    */

    /**
     * 计算出所给字节的二进制编码(单个字节)
     *
     * @param flag 判断是否要高位补位
     * @param str  需要转换的字节
     * @return 转换之后的二进制编码(补码)
     */
    public static String byteToBinaryCodeString(boolean flag, byte str) {
        // 将字节转换为int型,方便调用方法转二进制
        int temp = str;
        // 按位与
        if (flag) {
            temp |= 256;
        }
        // 转为二进制,返回的是二进制的补码
        String s = Integer.toBinaryString(temp);
        // 是正数就要补高位
        if (flag) {
            return s.substring(s.length() - 8);
        } else { // 否则返回这个二进制字符串
            return s;
        }
    }

    /**
     * 将经过处理后的赫夫曼编码数组转化为原始字符串的字符数组
     *
     * @param hashManCodes 哈夫曼编码表
     * @param hashManArr   处理后的赫夫曼数组
     * @return 原始字符串的字符数组
     */
    public static byte[] decode(Map<Byte, String> hashManCodes, byte[] hashManArr) {
        /* 1.将赫夫曼数组转化为二进制字符串,例如:[12,34,56,78,34,56] ---------> "000000010111111111111101"  */

        // 定义一个StringBuilder,用于拼接每个字符对应的二进制编码 例如:88---->000101000..
        StringBuilder stringBuilder = new StringBuilder();
        // 循环遍历赫夫曼数组,将每个字符转化为对应的二进制编码
        for (int i = 0; i < hashManArr.length; i++) {
            // 定义一个变量,用于接收遍历到的每个字符
            byte b = hashManArr[i];
            // 判断是否是最后一个字符
            boolean flag = (i == hashManArr.length - 1);
            // 得到每个字符的二进制编码
            String s = byteToBinaryCodeString(!flag, b);
            // 拼接处字符的二进制编码
            stringBuilder.append(s);
        }
        System.out.println("赫夫曼数组对应的二进制字符串是:" + stringBuilder.toString());

        /* 2.遍历(截取)得到的二进制字符串,通过查找赫夫曼表,将其转化为对应的字符 */

        // 逆向查找赫夫曼表,所以定义一个新的HashMap,存储字符及其对应的二进制 例如:0100-87
        HashMap<String, Byte> NewHashMap = new HashMap<>();
        // 遍历原始的赫夫曼编码表
        for (Map.Entry<Byte, String> entry : hashManCodes.entrySet()) {
            NewHashMap.put(entry.getValue(), entry.getKey());
        }
//        System.out.println("新的赫夫曼编码表是:" + NewHashMap);

        // 截取二进制编码,依次转化为对应的字符
        // 创建一个集合,存放字节
        List<Byte> ByteList = new ArrayList<>();
        // 遍历stringBuilder
        for (int i = 0; i < stringBuilder.length(); ) {
            int count = 1; // 用于动态截取二进制编码
            boolean flag = true; // 标志位
            Byte key = null; // 变量提升
            // 循环遍历二进制编码
            while (flag) {
                String str = stringBuilder.substring(i, i + count);
                key = NewHashMap.get(str);
                if (key == null) { // 匹配失败,继续找
                    count++;
                } else { // 匹配成功,退出循环
                    flag = false;
                }
            }
            // 将匹配到的字符添加到集合中
            ByteList.add(key);
            // 匹配下一个字节
            i += count;
        }
        // for循环结束后,集合中存放了原始数据的字符串
        // 3.要把这些字符串添加到一个字符数组中
        byte[] byteArr = new byte[ByteList.size()];
        for (int i = 0; i < ByteList.size(); i++) {
            byteArr[i] = ByteList.get(i);
        }
        return byteArr;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值