哈夫曼编码

news2024/9/24 17:21:25

哈夫曼编码

基本介绍

  1. 赫夫曼编码也翻译为 哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式, 属于一种程序算法
  2. 赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。
  3. 赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~90%之间
  4. 赫夫曼码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,称之为最佳编码

原理解析

  1. 通信领域中信息的处理方式1-定长编码
    将"i like like like java do you like a java"压缩再解压 // 共40个字符(包括空格)
    而对应的ASCII 码表它的编号为105 32 108 105 107 101 32 108 105 107 101 32 108 105 107 101 32 106 97 118 97 32 100 111 32 121 111 117 32 108 105 107 101 32 97 32 106 97 118 97 //对应Ascii码
    将它编码后每个字符对应的二进制01101001 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101010 01100001 01110110 01100001 00100000 01100100 01101111 00100000 01111001 01101111 01110101 00100000 01101100 01101001 01101011 01100101 00100000 01100001 00100000 01101010 01100001 01110110 01100001 //对应的二进制
    按照二进制来传递信息,总的长度是 359 (包括空格)

  2. 通信领域中信息的处理方式2-变长编码
    i like like like java do you like a java // 共40个字符(包括空格)
    d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9 // 各个字符对应的个数
    0= , 1=a, 10=i, 11=e, 100=k, 101=l, 110=o, 111=v, 1000=j, 1001=u, 1010=y, 1011=d 说明:按照各个字符出现的次数进行编码,原则是出现次数越多的,则编码越小,比如 空格出现了9 次, 编码为0 ,其它依次类推.
    按照上面给各个字符规定的编码,则我们在传输 “i like like like java do you like a java” 数据时,编码就是 10010110100…
    字符的编码都不能是其他字符编码的前缀,符合此要求的编码叫做前缀编码, 即不能匹配到重复的编码(这个在赫夫曼编码中,我们还要进行举例说明, 不捉急)

  3. 通信领域中信息的处理方式3-赫夫曼编码
    i like like like java do you like a java // 共40个字符(包括空格)
    d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9 // 各个字符对应的个数
    按照上面字符出现的次数构建一颗赫夫曼树, 次数作为权值.(图后)

在这里插入图片描述

例如:

//根据赫夫曼树,给各个字符
//规定编码 , 向左的路径为0
//向右的路径为1 , 编码如下:
o: 1000 u: 10010 d: 100110 y: 100111 i: 101
a : 110 k: 1110 e: 1111 j: 0000 v: 0001
l: 001 : 01

按照上面的赫夫曼编码,我们的"i like like like java do you like a java" 字符串对应的编码为 (注意这里我们使用的无损压缩)
1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110
长度为 : 133
说明:
原来长度是 359 , 压缩了 (359-133) / 359 = 62.9%
此编码满足前缀编码, 即字符的编码都不能是其他字符编码的前缀。不会造成匹配的多义性.

注意, 这个赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是wpl 是一样的,都是最小的, 比如: 如果我们让每次生成的新的二叉树总是排在权值相同的二叉树的最后一个,则生成的二叉树为:

在这里插入图片描述

最佳实践-数据压缩(创建赫夫曼树)

将给出的一段文本,比如 “i like like like java do you like a java” , 根据前面的讲的赫夫曼编码原理,对其进行数据压缩处理 ,形式如 “1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110”
步骤1:根据赫夫曼编码压缩数据的原理,需要创建 “i like like like java do you like a java” 对应的赫夫曼树.

  1. 先将这些字符串转化为byte数组
  2. 再将这些数组放到Map里面统计每个字符出现的次数
  3. 再将Map里面的元素转化成节点,方便生成对应的哈夫曼树
  4. 将节点数组转化成对应的哈夫曼树
  5. 这些从根节点到叶子节点的路径就是我们所求的哈夫曼编码,将这些编码存储到Map中
  6. 再扫描原始字符串,将他们按照哈夫曼编码表中的编码对应转换成相应的二进制
  7. 到这里转换完的二进制本质还是字符串,我们要将这些字符串转换成一个字节一个字节的二进制
  8. 将字符串转换成byte数组存放,这样就得到对应的哈夫曼编码后的(压缩)二进制
  9. 最后输出即可

步骤2:解码过程,就是编码的一个逆向操作

  1. 先将二进制字数组化成字符串
  2. 将一开始的Map中的键和值位置调换,值变为键,键变为值
  3. 扫描字符串,如果出现了相应的编号就将相应编号对应的值存放到byte数组集合中
  4. 再将得到的byte数组集合转换为字符串就完成了解压的操作

代码实现

节点类

package com.datestructures.tree.huffmancode;
//节点类
//为了让Node 对象支持排序Collections集合排序
//让Node 实现Comparable<Node>接口

public class Node implements Comparable<Node> {
    Byte data;//存放数据本身  'a'=>97  'b'=>98 ' '=32
    int weight;//节点权值 表示节点出席的次数
    Node left;//指向左子节点
    Node right;//指向右子节点

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

    //前序遍历
    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 "Node{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }

    @Override
    public int compareTo(Node o) {
        //从小到大排序
        return this.weight - o.weight;
    }
}

先将这些字符串转化为对应的Byte数组

String content = "i like like like java do you like a java";
byte[] contentBytes = content.getBytes();//将字符串转成字节数组

将这些数组放到Map里面统计每个字符出现的次数

再将Map里面的元素转化成节点,方便生成对应的哈夫曼树

 /**
     * 将byte数组转成节点集合
     *
     * @param bytes 接收字节数组
     * @return 返回的就是List  形式[Node[date=97,weight=5],Node[date=32,weight=9]......]
     */
    private static List<Node> getNodes(byte[] bytes) {
        //1创建一个ArrayList
        ArrayList<Node> nodes = new ArrayList<>();
        //遍历bytes 统计每个byte出现的次数  用map较好   map[key,value]
        Map<Byte, Integer> counts = new HashMap<>();
        for (byte b : bytes) {
            Integer count = counts.get(b);
            if (count == null) {//Map中没有这个字符
                counts.put(b, 1);
            } else {
                counts.put(b, count + 1);
            }
        }
        //把每个键值对转成Node对象  保存到nodes集合中
        //遍历map
        for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
            nodes.add(new Node(entry.getKey(), entry.getValue()));
        }
        return nodes;
    }

再将这些节点转化成对应的哈夫曼树

//通过List创建对应的哈夫曼树
    private static Node createHuffmanTree(List<Node> nodes) {
        while (nodes.size() > 1) {
            //排序,从小到大
            Collections.sort(nodes);
            //取出第一课最小的二叉树
            Node leftNode = nodes.get(0);
            //取出第二课小的二叉树
            Node rightNode = nodes.get(1);
            //创建一棵新的二叉树,它的根节点没有data,只有权值
            Node parent = new Node(null, leftNode.weight + rightNode.weight);
            parent.left = leftNode;
            parent.right = rightNode;
            //将已经处理过的两颗二叉树移除
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //将新的二叉树加入到nodes
            nodes.add(parent);
        }
        //nodes最后的节点就是哈夫曼树的根节点
        return nodes.get(0);
    }

这些从根节点到叶子节点的路径就是我们所求的哈夫曼编码,将这些编码存储到Map中

//生成的哈夫曼树对应的哈夫曼编码
    //思路
    //1.将哈夫曼编码表存放在Map<Byte,string>  形式 32=>01  97=>100  100=>1100 等等
    static Map<Byte, String> huffmanCodes = new HashMap<>();
    //2.在生成哈夫曼编码表时,需要去拼接路径,定义一个StringBuilder 存储某个叶子结点的路径
    static StringBuilder stringBuilder = new StringBuilder();

    //为了调用方便  重载getCodes
    private static Map<Byte, String> getCodes(Node root) {
        if (root == null) {
            return null;
        }
        //处理root左子树
        getCodes(root.left, "0", stringBuilder);
        getCodes(root.right, "1", stringBuilder);
        return huffmanCodes;
    }

    /**
     * 功能:将传入的node节点的所有叶子结点的哈夫曼编码得到并放入到huffmanCodes集合中
     *
     * @param node          传入节点 默认根节点
     * @param code          路径:左子节点是0 右子节点是1
     * @param stringBuilder 用于拼接路径
     */
    private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        //将code加入到stringBuilder2
        stringBuilder2.append(code);
        if (node != null) {//如果node==null不处理
            //判断当前node是叶子结点还是非叶子节点
            if (node.data == null) {//非叶子节点
                //递归处理
                //向左递归
                getCodes(node.left, "0", stringBuilder2);
                //向右递归
                getCodes(node.right, "1", stringBuilder2);
            } else {//说明是一个叶子结点
                //就表示找到某个叶子结点的最后
                huffmanCodes.put(node.data, stringBuilder2.toString());

            }

        }
    }

再扫描原始字符串,将他们按照哈夫曼编码表中的编码对应转换成相应的二进制

到这里转换完的二进制本质还是字符串,我们要将这些字符串转换成一个字节一个字节的二进制

将字符串转换成byte数组存放,这样就得到对应的哈夫曼编码后的(压缩)二进制

//将字符串对应的byte[]数组,通过生成的哈夫曼编码表,返回一个哈夫曼编码  压缩后的byte[]
    /**
     * @param bytes        这是原始的字符串对应的byte[]
     * @param huffmanCodes 生成的哈夫曼编码map
     * @return 返回哈夫曼编码处理后的byte[]
     * 举例:
     * String content = "i like like like java do you like a java" => byte[] contentBytes = content.getBytes();
     * 返回的是这个字符串对应的byte[] huffmanCodeBytes  ,即8位对应一个byte,放入到hufffmanCodeBytes
     * huffmanCodeBytes[0] = 10101000(补码)=>byte [推导 10101000=> 10101000-1=>10100111(反码)=>11011000(原码)=>-88]
     * huffmanCodeBYtes[1] = -88
     */
    private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
        //1.先利用哈夫曼编码表将bytes转成哈夫曼编码后的字符串
        StringBuilder stringBuilder = new StringBuilder();
        //遍历bytes数组  得到哈夫曼编码后的字符串
        for (byte b : bytes) {
            stringBuilder.append(huffmanCodes.get(b));
        }
        System.out.println("原始压缩二进制码" + stringBuilder);
        //2.将字符串转成byte数组
        //统计返回 byte[] huffmanCodeBytes 长度
        // 统计返回字节大小
        int len = (stringBuilder.length() + 7) / 8;
        //创建存储压缩后的bytes[]
        byte[] huffmanCodeBytes = new byte[len];
        int index = 0;//记录是第几个byte
        for (int i = 0; i < stringBuilder.length(); i += 8) {//每8位对应一个byte,所以步长+8
            String strByte;
            if (i + 8 > stringBuilder.length()) {//不够8位
                strByte = stringBuilder.substring(i);
            } else {
                strByte = stringBuilder.substring(i, i + 8);
            }
            //将strByte转成一个byte,放入到huffmanCodeBytes
            huffmanCodeBytes[index++] = (byte) Integer.parseInt(strByte, 2);
        }
        return huffmanCodeBytes;
    }

步骤2代码实现

//完成数据解压
    /*
    1.将huffmanCodeBytes [-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]重新转成哈夫曼编码对应的二进制字符串
    2.对照哈夫曼编码转成对应的字符串
     */
    //编写一个方法  完成对压缩数据的解码

    /**
     * @param huffmanCodes 原先使用的哈夫曼编码表
     * @param huffmanBytes 经过哈夫曼编码得到的字节数组
     * @return 返回原来字符串对应的数组
     */
    private static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {
        //1.先得到huffmanBytes  对应的二进制字符串  形式 1010100010111...
        StringBuilder stringBuilder = new StringBuilder();
        //2.将byte[] 转成二进制的字符串
        for (int i = 0; i < huffmanBytes.length; i++) {
            //判断是不是最后一个字节
            boolean flag = (i == huffmanBytes.length - 1);
            stringBuilder.append(byteToBitString(!flag, huffmanBytes[i]));
        }
        //把字符串按照指定的哈夫曼编码进行解码
        //把哈夫曼编码表进行调换  因为反向查询  压缩 a -> 100  解码 100 -> a
        Map<String, Byte> map = new HashMap<>();
        for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
            map.put(entry.getValue(), entry.getKey());
        }
        //创建一个集合  存放byte
        List<Byte> list = new ArrayList<>();
        // i可以理解成索引 一直扫描stringBuilder
        for (int i = 0; i < stringBuilder.length();) {
            int count = 1;//扫描得到对应编码 计数器
            boolean flag = true;
            Byte b = null;
            while (flag) {
                // 递增的取出key  取出一个'1'  '0';
                String key = stringBuilder.substring(i, i + count);//i不动 让count移动  直到匹配到一个字符
                b = map.get(key);
                if (b != null) {
                    flag = false;
                } else {//说明没有匹配到
                    count++;
                }
            }
            list.add(b);//将字符放到集合中
            i += count;//让i 直接移动到count+i
        }
        //当for循环结束后list存放了所有的字符
        //把list中的数组放到byte[] 并返回
        byte[] b = new byte[list.size()];
        for (int i = 0; i < b.length; i++) {
            b[i] = list.get(i);
        }
        return b;
    }

完整代码

节点类

package com.datestructures.tree.huffmancode;
//节点类
//为了让Node 对象支持排序Collections集合排序
//让Node 实现Comparable<Node>接口

public class Node implements Comparable<Node> {
    Byte data;//存放数据本身  'a'=>97  'b'=>98 ' '=32
    int weight;//节点权值 表示节点出席的次数
    Node left;//指向左子节点
    Node right;//指向右子节点

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

    //前序遍历
    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 "Node{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }

    @Override
    public int compareTo(Node o) {
        //从小到大排序
        return this.weight - o.weight;
    }
}

哈夫曼编码类

package com.datestructures.tree.huffmancode;

import java.util.*;

public class HuffmanCode {
    public static void main(String[] args) {
        String content = "i like like like java do you like a java";
        byte[] contentBytes = content.getBytes();//将字符串转成字节数组
        System.out.println(contentBytes.length);//40
        byte[] huffmanCodeBytes = huffmanZip(contentBytes);
        System.out.println("压缩后的结果" + Arrays.toString(huffmanCodeBytes));
        /*分布过程
        List<Node> nodes = getNodes(contentBytes);//将字节数组转成节点集合
        System.out.println(nodes);
        //测试一把  创建的二叉树
        System.out.println("哈夫曼树");
        Node root = createHuffmanTree(nodes);
        System.out.println("前序遍历一下");
        preOrder(root);
        //测试一把  是否生成了哈夫曼编码
        Map<Byte,String> huffmanCodes = getCodes(root);
        System.out.println(huffmanCodes);
        //测试 哈夫曼编码过后的byte[]
        byte[] huffmanCodeBytes = zip(contentBytes,huffmanCodes);
        System.out.println(Arrays.toString(huffmanCodeBytes));
        */

        //如何将数据进行解压  解码
        //测试一把byteToBinString方法
        //System.out.println(byteToBitString(false,(byte)-1));
        byte[] sourceByre = decode(huffmanCodes, huffmanCodeBytes);
        System.out.println("原来的字符串"+new String(sourceByre));
    }
    //完成数据解压
    /*
    1.将huffmanCodeBytes [-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]重新转成哈夫曼编码对应的二进制字符串
    2.对照哈夫曼编码转成对应的字符串
     */
    //编写一个方法  完成对压缩数据的解码

    /**
     * @param huffmanCodes 原先使用的哈夫曼编码表
     * @param huffmanBytes 经过哈夫曼编码得到的字节数组
     * @return 返回原来字符串对应的数组
     */
    private static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {
        //1.先得到huffmanBytes  对应的二进制字符串  形式 1010100010111...
        StringBuilder stringBuilder = new StringBuilder();
        //2.将byte[] 转成二进制的字符串
        for (int i = 0; i < huffmanBytes.length; i++) {
            //判断是不是最后一个字节
            boolean flag = (i == huffmanBytes.length - 1);
            stringBuilder.append(byteToBitString(!flag, huffmanBytes[i]));
        }
        //把字符串按照指定的哈夫曼编码进行解码
        //把哈夫曼编码表进行调换  因为反向查询  压缩 a -> 100  解码 100 -> a
        Map<String, Byte> map = new HashMap<>();
        for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
            map.put(entry.getValue(), entry.getKey());
        }
        //创建一个集合  存放byte
        List<Byte> list = new ArrayList<>();
        // i可以理解成索引 一直扫描stringBuilder
        for (int i = 0; i < stringBuilder.length();) {
            int count = 1;//扫描得到对应编码 计数器
            boolean flag = true;
            Byte b = null;
            while (flag) {
                // 递增的取出key  取出一个'1'  '0';
                String key = stringBuilder.substring(i, i + count);//i不动 让count移动  直到匹配到一个字符
                b = map.get(key);
                if (b != null) {
                    flag = false;
                } else {//说明没有匹配到
                    count++;
                }
            }
            list.add(b);//将字符放到集合中
            i += count;//让i 直接移动到count+i
        }
        //当for循环结束后list存放了所有的字符
        //把list中的数组放到byte[] 并返回
        byte[] b = new byte[list.size()];
        for (int i = 0; i < b.length; i++) {
            b[i] = list.get(i);
        }
        return b;
    }

    /**
     * 将一个byte 转成对应的二进制字符串
     *
     * @param b
     * @param flag 标识是否需要补高位  如果是true 则需要补高位  如果是false  则不需要补   如果是最后一个字节  则不需要补高位
     *             因为转成字节码的时候也没有补8位 而是直接加到当时转成的字符串
     * @return 对应的二进制字符串(按补码返回)
     */
    private static String byteToBitString(boolean flag, byte b) {
        //使用一个变量保存b
        int temp = b;//将b转成int
        //如果是正数  还存在一个补高位  返回的是补码  正数补码为它本身
        if (flag) {
            temp |= 256;
            ;//按位与  256  1 0000 0000 如果是1  0000 0001 =>按位与 | => 1 0000 0001
        }
        String str = Integer.toBinaryString(temp);//返回的是temp对应的二进制补码
        if (flag) {
            return str.substring(str.length() - 8);//返回二进制后8位
        } else {
            return str;
        }
    }

    /**
     * 使用一个方法,将前面的方法封装起来  便于我们的调用
     *
     * @param contentBytes 原始的字符串对应的数组
     * @return 返回的是经过哈夫曼编码处理后的字节数组  压缩后的数组
     */
    private static byte[] huffmanZip(byte[] contentBytes) {
        List<Node> nodes = getNodes(contentBytes);//将字节数组转成节
        //根据nodes创建哈夫曼树
        Node root = createHuffmanTree(nodes);
        //根据哈夫曼树生成对应的哈夫曼编码
        Map<Byte, String> huffmanCodes = getCodes(root);
        //根据哈夫曼编码得到压缩后的哈夫曼编码字节数组
        byte[] huffmanCodeBytes = zip(contentBytes, huffmanCodes);
        return huffmanCodeBytes;
    }
    //将字符串对应的byte[]数组,通过生成的哈夫曼编码表,返回一个哈夫曼编码  压缩后的byte[]
    /**
     * @param bytes        这是原始的字符串对应的byte[]
     * @param huffmanCodes 生成的哈夫曼编码map
     * @return 返回哈夫曼编码处理后的byte[]
     * 举例:
     * String content = "i like like like java do you like a java" => byte[] contentBytes = content.getBytes();
     * 返回的是这个字符串对应的byte[] huffmanCodeBytes  ,即8位对应一个byte,放入到hufffmanCodeBytes
     * huffmanCodeBytes[0] = 10101000(补码)=>byte [推导 10101000=> 10101000-1=>10100111(反码)=>11011000(原码)=>-88]
     * huffmanCodeBYtes[1] = -88
     */
    private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
        //1.先利用哈夫曼编码表将bytes转成哈夫曼编码后的字符串
        StringBuilder stringBuilder = new StringBuilder();
        //遍历bytes数组  得到哈夫曼编码后的字符串
        for (byte b : bytes) {
            stringBuilder.append(huffmanCodes.get(b));
        }
        System.out.println("原始压缩二进制码" + stringBuilder);
        //2.将字符串转成byte数组
        //统计返回 byte[] huffmanCodeBytes 长度
        // 统计返回字节大小
        int len = (stringBuilder.length() + 7) / 8;
        //创建存储压缩后的bytes[]
        byte[] huffmanCodeBytes = new byte[len];
        int index = 0;//记录是第几个byte
        for (int i = 0; i < stringBuilder.length(); i += 8) {//每8位对应一个byte,所以步长+8
            String strByte;
            if (i + 8 > stringBuilder.length()) {//不够8位
                strByte = stringBuilder.substring(i);
            } else {
                strByte = stringBuilder.substring(i, i + 8);
            }
            //将strByte转成一个byte,放入到huffmanCodeBytes
            huffmanCodeBytes[index++] = (byte) Integer.parseInt(strByte, 2);
        }
        return huffmanCodeBytes;
    }

    //生成的哈夫曼树对应的哈夫曼编码
    //思路
    //1.将哈夫曼编码表存放在Map<Byte,string>  形式 32=>01  97=>100  100=>1100 等等
    static Map<Byte, String> huffmanCodes = new HashMap<>();
    //2.在生成哈夫曼编码表时,需要去拼接路径,定义一个StringBuilder 存储某个叶子结点的路径
    static StringBuilder stringBuilder = new StringBuilder();

    //为了调用方便  重载getCodes
    private static Map<Byte, String> getCodes(Node root) {
        if (root == null) {
            return null;
        }
        //处理root左子树
        getCodes(root.left, "0", stringBuilder);
        getCodes(root.right, "1", stringBuilder);
        return huffmanCodes;
    }

    /**
     * 功能:将传入的node节点的所有叶子结点的哈夫曼编码得到并放入到huffmanCodes集合中
     *
     * @param node          传入节点 默认根节点
     * @param code          路径:左子节点是0 右子节点是1
     * @param stringBuilder 用于拼接路径
     */
    private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        //将code加入到stringBuilder2
        stringBuilder2.append(code);
        if (node != null) {//如果node==null不处理
            //判断当前node是叶子结点还是非叶子节点
            if (node.data == null) {//非叶子节点
                //递归处理
                //向左递归
                getCodes(node.left, "0", stringBuilder2);
                //向右递归
                getCodes(node.right, "1", stringBuilder2);
            } else {//说明是一个叶子结点
                //就表示找到某个叶子结点的最后
                huffmanCodes.put(node.data, stringBuilder2.toString());

            }

        }
    }

    //前序遍历的方法
    private static void preOrder(Node root) {
        if (root != null) {
            root.preOrder();
        } else {
            System.out.println("空树,不能遍历");
        }
    }

    /**
     * 将byte数组转成节点集合
     *
     * @param bytes 接收字节数组
     * @return 返回的就是List  形式[Node[date=97,weight=5],Node[date=32,weight=9]......]
     */
    private static List<Node> getNodes(byte[] bytes) {
        //1创建一个ArrayList
        ArrayList<Node> nodes = new ArrayList<>();
        //遍历bytes 统计每个byte出现的次数  用map较好   map[key,value]
        Map<Byte, Integer> counts = new HashMap<>();
        for (byte b : bytes) {
            Integer count = counts.get(b);
            if (count == null) {//Map中没有这个字符
                counts.put(b, 1);
            } else {
                counts.put(b, count + 1);
            }
        }
        //把每个键值对转成Node对象  保存到nodes集合中
        //遍历map
        for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
            nodes.add(new Node(entry.getKey(), entry.getValue()));
        }
        return nodes;
    }

    //通过List创建对应的哈夫曼树
    private static Node createHuffmanTree(List<Node> nodes) {
        while (nodes.size() > 1) {
            //排序,从小到大
            Collections.sort(nodes);
            //取出第一课最小的二叉树
            Node leftNode = nodes.get(0);
            //取出第二课小的二叉树
            Node rightNode = nodes.get(1);
            //创建一棵新的二叉树,它的根节点没有data,只有权值
            Node parent = new Node(null, leftNode.weight + rightNode.weight);
            parent.left = leftNode;
            parent.right = rightNode;
            //将已经处理过的两颗二叉树移除
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //将新的二叉树加入到nodes
            nodes.add(parent);
        }
        //nodes最后的节点就是哈夫曼树的根节点
        return nodes.get(0);
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/498369.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Variable used in lambda expression should be final or effectively final

场景描述 我们在使用Java8 lambda表达式的时候时不时会遇到这样的编译报错&#xff1a; 这句话的意思是&#xff0c;lambda 表达式中使用的变量应该是 final 或者有效的 final&#xff0c;为什么会有这种规定&#xff1f; 匿名类中的局部变量 其实在 Java 8 之前&#xff0…

【iOS】—— RunLoop线程常驻和线程保活

文章目录 没有线程常驻会怎么样&#xff1f; 线程常驻线程保活 没有线程常驻会怎么样&#xff1f; 我们一般写一个子线程&#xff0c;子线程执行完分配的任务后就会自动销毁&#xff0c;比如下面这个情况&#xff1a; 我们先重写一下NSThread里面的dealloc方法&#xff0c;打印…

如何在测试中让H2支持JSONB

如今在开发系统时&#xff0c;有各种各样的数据库供我们选择。之前我们在博客基于MariaDB4j实现持久层单元测试介绍了使用MariaD4j代替作为MySQL的替身执行单元测试&#xff0c;但是并不是所有的数据库都能找到合适的替身来执行单元测试。 今天作者在写测试的过程中就遇到了一…

AutoSar标准官网下载

文章目录 打开官方网站ECU的开发基本遵循标准为Classic Platform选择相应模块&#xff0c;此框图链接为最新标准&#xff0c;也可在下方选择历史版本跳转进来后&#xff0c;可以选择下载所有文档&#xff0c;也可以按需下载Autosar文档命名&#xff1a;AUTOSAR类型模块名称 打开…

2023 年 3 月青少年机器人技术等级考试理论综合试卷(六级)

2023 年 3 月青少年机器人技术等级考试理论综合试卷&#xff08;六级&#xff09; 一、单选题(共 20 题&#xff0c;共 80 分) 1. ESP32 for Arduino I C 类库的成员函数 beginTransmissio()中&#xff0c;下列描述正确的是&#xff1f;&#xff08; C&#xff09; A. 初始化&a…

Java 基础进阶篇(十一)—— 泛型的定义与深入

文章目录 一、泛型概述二、泛型的定义2.1 泛型类2.2 泛型方法2.3 泛型接口 三、泛型深入3.1 泛型通配符3.2 泛型上下限3.3 案例&#xff1a;定义一个 “所有车量进行比赛” 的方法 一、泛型概述 泛型是 JDK5 中引入的特性&#xff0c;可以在编译阶段约束操作的数据类型&#x…

Illustrator如何绘制图形对象之实例演示?

文章目录 0.引言1.几何图形绘制樱花2.绘制一艘潜水艇3.调整透视网格 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对Illustrator进行了学习&#xff0c;本文通过《Illustrator CC2018基础与实战》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对图…

【C++】 类基础汇总(类封装,构造、析构函数...)

目录 前言 正文 类封装 为什么要进行类封装 概念 访问修饰符 构造函数 概念 特点 析构函数 概念 特点 再谈面向过程与面向对象 面向过程 代码举例 面向对象 代码举例 结语 下期预告 前言 在学习过【C语言进阶C】 C基础--让你丝滑的从C语言进阶到C 之后&am…

Batch v.s. Stream Processing

当处理大数据时&#xff0c;通常使用批处理和流处理两种模型。它们的主要区别如下&#xff1a; 1.输入 批处理处理的是时间边界确定的数据&#xff0c;也就是输入数据有一个结尾。 流处理处理的是数据流&#xff0c;没有明确定义的边界。 2.实时性 批处理通常用于数据不需要实时…

2023最新水果DAW编曲软件fl studio 21.0.3.351中文版功能介绍/下载安装/语言切换/激活解锁教程

2023最新水果DAW编曲软件fl studio 21.0.3.351中文版功能介绍/下载安装/语言切换/激活解锁教程 是一款免费的音乐编曲制作软件&#xff0c;有了它你可以制作出色的音乐。它为您提供了一个集成的开发环境&#xff0c;使用起来非常简单有效&#xff0c;您的工作会变得更有条理。同…

2022年NOC大赛编程马拉松赛道python小高组试卷-正式卷,包含答案

2022NOC-Python决赛小高组A卷正式卷 单选题: 1、答案:D Python中关于自定义函数,下列说法正确的是? A、函值一定有返回值 B、函数一定有参数 C、函数内一定要定义变量 D、以上三种说法都不对 2、答案:A 下列说法错误的是? A、二维列表里的元素一定是一维列表 B…

VS2015下写Qt代码qDebug()函数不能看到调试信息

使用VS2015调试Qt代码发现不能很好的显示qDebug()的内容. 例如:我想显示 qDebug() << key << ": " << value.toString(); 这个代码中的值,想把他打印到控制台上.但是我写的是UI软件,并没有控制台显示.这时候就需要在exe的属性中设置一下.以我写…

[LeetCode周赛复盘] 第 344 场周赛20230507

[LeetCode周赛复盘] 第 344 场周赛20230507 一、本周周赛总结6416. 找出不同元素数目差数组1. 题目描述2. 思路分析3. 代码实现 6417. 频率跟踪器1. 题目描述2. 思路分析3. 代码实现 6418. 有相同颜色的相邻元素数目1. 题目描述2. 思路分析3. 代码实现 6419. 使二叉树所有路径…

MySQL_2 常见列类型与表的基本操作

目录 一、常见列类型&#xff08;字段类型&#xff09; 1.数值类型 : 1 整型 2 浮点型 2.文本类型&#xff08;字符串类型&#xff09; : 3.二进制类型 : 4.日期类型 : 二、表的基本操作 1.创建表 : 1 基本语法 2 代码演示 2.删除表 : 1 基本语法 2 代码演示 3.修改表…

收藏:不错的质量论述文:《研发效能系列 - 质量与速度能否兼得》

研发效能系列 - 质量与速度能否兼得丨IDCF 引言 我们的时间&#xff0c;应该是用于提高软件质量&#xff0c;还是专注在发布更有价值的功能&#xff1f;这貌似是软件研发中永恒的话题。 到底什么是质量&#xff1f; 质量有什么特质&#xff1f; 质量与速度是什么关系&#…

Actuators + jolokia

Actuators + jolokia Jolokia造成的XXE漏洞 首先我们查看我们当前环境http://x.x.x.x/jolokia/list地址,是否存在reloadByURL这个方法, 这个方法是造成RCE的关键。因为logback组件提供的reloadByURL操作使我们可以从外部URL重新加载日志配置 创建logback.xml和file.dtd文件…

为何要使用MySQL?MySQL和Oracle的区别有什么?

目录 一、为何要使用MySQL&#xff1f;二、MySQL学习路线三、数据库相关概念1、DB&#xff0c;数据库Database。2、DBMS&#xff0c;数据库管理系统Database Management System。3、SQL&#xff0c;结构化查询语言&#xff0c;Structured Query Language。 四、常见的关系型数据…

SpringCloud_Config配置中心和Bus消息总线和Stream消息驱动

文章目录 一、SpringCloudConfig配置中心1、SpringCloudConfig配置中心的概论2、SpringCloudConfig配置中心的gitee仓库搭建3、SpringCloudConfig配置中心服务端的搭建4、SpringCloudConfig配置中心客户端的的搭建5、SpringCloudConfig配置中心客户端动态刷新配置文件 二、Spri…

如何用ChatGPT做品牌联名方案策划?

该场景对应的关键词库&#xff08;15个&#xff09;&#xff1a; 品牌、个人IP、社交话题、联名策划方案、调研分析、市场影响力、资源互补性、产品体验、传播话题、视觉形象设计、合作职权分配、销售转化、曝光目标、宣发渠道、品牌形象 提问模板&#xff08;1个&#xff09;…

Milvus应用开发实战【语义搜索】

美国总统竞选活动即将到来。 现在是回顾拜登政府上任头两年的一些演讲的好时机。 搜索一些演讲记录以了解更多关于白宫迄今为止关于某些主题的信息不是很好吗&#xff1f; 假设我们要搜索演讲的内容。 我们该怎么做&#xff1f; 我们可以使用语义搜索。 语义搜索是目前人工智能…