• Huffman编码和解码


    一.Huffman树

    定义:  给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径达到最小,这样的二叉树称为最优二叉树,也称为霍夫曼树(Huffman树).

    特点:     Huffman树是带权路径长度最短的树,权值较大的节点离根节点较近

        权值 = 当前节点的值 * 层数,wpl最小的值,就是Huffman树

                       

    创建步骤  举例  {13,7,8,3,29,6,1}

        1.从小到大进行排序,将每一个数据视为一个节点,每一个节点都可视为一个二叉树

        2.取出根节点权值两个最小的二叉树

        3.组成一个新的二叉树,新的二叉树根节点的权值是前面两颗二叉树节点权值之和

        4.再将这颗二叉树以根节点的权值大小进行再排序,不断重复1,2,3,4步,直到所有的数据都被处理,就得到一个Huffman树

    class Node implements Comparable<Node> {
       // 实现Comparable接口,可以使用Collections工具类进行排序
        public int value;
        public Node left;
        public Node right;
    
        public Node(int value) {
            this.value = value;
        }
    
        public Node() {
        }
        
        /*用于测试Huffman树是否正确*/
        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{" +
                    "value=" + value +
                    '}';
        }
    
        @Override
        public int compareTo(Node o) { // 从小到大进行排序
            return this.value - o.value;
        }
    }
     /**
         * 创建霍夫曼树
         * @param array 原数组
         * @return 创建好Huffman树的root节点
         */
        public static Node createHuffmanTree(int[] array){
            if (array.length == 0 || array == null){
                System.out.println("数组为空,无法创建");
                return null;
            }
            /*遍历数组中的每一个元素,构造成Node,放入到List中*/
            List<Node> nodes = new ArrayList<>();
            for (int item : array) {
                nodes.add(new Node(item));
            }
    
            while (nodes.size() > 1){ /*只要List中有元素,就一直进行权值计算*/
                /*对Node进行排序*/
                Collections.sort(nodes);
    
                /*取出根节点两个权值最小的二叉树*/
                Node leftNode = nodes.get(0);
                Node rightNode = nodes.get(1);
    
                /*构建一个新的二叉树*/
                Node parent = new Node(leftNode.value + rightNode.value);
                parent.left = leftNode;
                parent.right = rightNode;
    
                /*从List中删除使用过的节点*/
                nodes.remove(leftNode);
                nodes.remove(rightNode);
                /*将新的节点加入到List中*/
                nodes.add(parent);
            }
            /*返回Huffman树的root节点*/
            return nodes.get(0); 
        }
    }    

    测试,如果生成的Huffman树是正确的,那么前序遍历的结果也是正确的

    public static void main(String[] args) {
            int[] array = {13,7,8,3,29,6,1};
            preOrder(createHuffmanTree(array));
        }
    
        public static void preOrder(Node root){
            if (root != null){
                root.preOrder();
            }else {
                System.out.println("该树为空,不能遍历");
                return;
            }
        }

                    

    二.Huffman编码

    定义:    Huffman编码是一种通信的编码,是在电通信领域的基本编码之一

    作用:  Huffman编码广泛的应用于数据文件的压缩,而且它是前缀编码,可以有效的节省传输的带宽

    编码的步骤:     举例  String content = 'i like like like java do you like a java oh oh oh';

      1.生成节点   

    /*定义节点,data用于存放数据,weight用于存放权值*/
    class HuffmanNode implements Comparable<HuffmanNode>{
        public Byte data;
        public int weight;
        public HuffmanNode left;
        public HuffmanNode right;
    
        public HuffmanNode(Byte data, int weight) {
            this.data = data;
            this.weight = weight;
        }
    
        public HuffmanNode() {
        }
    
        @Override
        public int compareTo(HuffmanNode o) {
            return this.weight - o.weight;
        }
        
    }

      2.统计字符串中每一个字符出现的次数

    /*统计字符串中每个字符出现的次数,放在List中进行返回*/
    /*List存储格式 [Node[date=97 ,weight = 5], Node[date=32,weight = 9]......]*/
    public static List<HuffmanNode> getNodes(byte[] bytes){
            if (bytes.length == 0 || bytes == null){
                System.out.println("字符串为空,无法进行编码");
                return null;
            }
            List<HuffmanNode> nodes = new ArrayList<>();
            Map<Byte,Integer> counts = new HashMap<>();
            /*遍历bytes ,统计每一个byte出现的次数*/
            for (byte item : bytes) {
               Integer count = counts.get(item);
               if (count == null){ // Map中没有这个字符,说明是第一次
                   counts.put(item,1);
               }else {
                   counts.put(item,count+1);
               }
            }
             /*遍历Map,将键值对转换为Node对象进行存放到List中*/
            for (Map.Entry<Byte,Integer> node:counts.entrySet()){
                nodes.add(new HuffmanNode(node.getKey(),node.getValue()));
            }
            return nodes;
        }

      3.根据List集合,创建Huffm树

    public static HuffmanNode createHuffmanTree(List<HuffmanNode> nodes){
            if (nodes.size() == 0 || nodes == null){
                System.out.println("生成的List为空,不能生成霍夫曼树");
                return null;
            }
            while (nodes.size() > 1){
                Collections.sort(nodes);
    
                HuffmanNode leftNode = nodes.get(0);
                HuffmanNode rightNode = nodes.get(1);
                HuffmanNode parent = new HuffmanNode(null,leftNode.weight+rightNode.weight);
    
                parent.left = leftNode;
                parent.right = rightNode;
    
                nodes.remove(leftNode);
                nodes.remove(rightNode);
                nodes.add(parent);
    
            }
            return nodes.get(0);
        }

      4.将传入的Huffman树进行Huffman编码

    /*将传入所有节点的Node节点的Huffman编码得到*/
    /*node 传入的节点*/
    /*code 路径,向左为0,向右为1*/
    /*StringBuild 用于拼接路径,生成编码*/
    public static void getCode(HuffmanNode node,String code,StringBuilder stringBuilder){
            StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
            /*将code加入到stringBuilder2 中*/
            stringBuilder2.append(code);
            if (node!=null){ // 如果node是null,则不进行处理
                if (node.data == null){ // 是非叶子节点
                    //向左递归
                    getCode(node.left,"0",stringBuilder2);
                    //向右递归
                    getCode(node.right,"1",stringBuilder2);
                }else { /*此时表明是叶子结点,说明找到了一条路径的最后*/
                    huffmanCode.put(node.data,stringBuilder2.toString());
                }
            }
        }
    
    /*方便调用,重载此方法*/
    public static Map<Byte,String> getCode(HuffmanNode root){
            if (root == null){
                System.out.println("没有生成霍夫曼树");
                return null;
            }else {
                /*处理root左子树*/
                getCode(root.left,"0",stringBuilder);
                /*处理root右子树*/
                getCode(root.right,"1",stringBuilder);
            }
            return huffmanCode;
        }

      5.使用Huffman编码进行压缩

    /*将字符串对应的byte数组,通过生成的Huffman编码表,返回一个Huffman编码压缩后的byte数组*/
    /*bytes 原始字符串对应的字节数组*/
    /*huffmanCode 生成的Huffman编码表*/
    /* 返回Huffman编码处理后的字节数组*/
    public static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCode){
            if (bytes.length == 0 || bytes == null){
                System.out.println("字符串为空,无法进行编码");
                return null;
            }
            /*1.根据HuffmanCode获取原始的字节数组的二进制的字符串*/
            StringBuilder stb = new StringBuilder();
            for (byte b : bytes) {
                stb.append(huffmanCode.get(b));
            }
            /*2.创建存储压缩后的字节数组*/
            int index = 0; //记录第几个byte
            int len = 0; // 确定霍夫曼编码的长度
            if (stb.length() % 8 == 0){
                len = stb.length() / 8;
            }else {
                len = stb.length() / 8 + 1;
            }
            byte[] huffmanCodeBytes = new byte[len];
            /*每8位对应一个byte,所以步长+8*/
            for (int i = 0; i < stb.length();i+=8){
                String strByte = null;
                if (i+8 > stb.length()){ // 不够8位,直接从当前截取到末尾
                    strByte = stb.substring(i); 
                }else {
                    strByte = stb.substring(i,i+8); //否则按照每8位进行拼接
                }
                /*将strByte 转换成一个个byte,放在要返回的字节数组中,进行返回*/
                huffmanCodeBytes[index++] = (byte)Integer.parseInt(strByte,2);
            }
            return huffmanCodeBytes;
        }

    查看编码的结果:   压缩率   (49-21) / 49 = 57.14%

    压缩之后的字节数组是:

    将上述Huffman编码的步骤封装

    public static byte[] getZip(byte[] bytes){
            List<HuffmanNode> nodes = getNodes(bytes);
            // 根据nodes创建赫夫曼树
            HuffmanNode root = createHuffmanTree(nodes);
            // 根据root节点生成霍夫曼编码
            huffmanCode = getCode(root);
            // 根据霍夫曼编码,对数据进行压缩,得到字节数组
            byte[] huffmanCodeBytes = zip(bytes,huffmanCode);
           // System.out.println(Arrays.toString(huffmanCodeBytes));
            return huffmanCodeBytes;
        }

    三.使用Huffman进行解码

      1.将一个二进制的byte,装换为二进制的字符串

    /**
         * 将一个byte转换成二进制的字符串
         * @param flag 表示是否要进行补高位,如果是true则需要补高位,false则不需要补位,如果是最后一个字节不需要补高位
         * @param b
         * @return  是该byte对应的二进制字符串(补码返回)
         */
        public static String byteToBitString(boolean flag,byte b){
            int temp = b;  /* 使用临时变量,将byte转换为int*/
            if (flag){ /*如果是一个正数,需要进行补位操作*/
                temp |= 256; /*按位与操作*/
            }
            String str = Integer.toBinaryString(temp); /*返回temp对应的二进制补码*/
            if (flag){ // 如果有8位,则按照8位来返回,否则直接返回字符串
                return str.substring(str.length()-8);
            }else {
                return str;
            }
        }

      2.解码操作

     /**
         *
         * @param huffmanCode   对应霍夫曼编码表
         * @param huffmanBytes  霍夫曼编码得到的字节数组
         * @return 原先字符串对应的字节数组 
         */
        public static byte[] decode(Map<Byte,String> huffmanCode,byte[] huffmanBytes){
            /*1.先得到HuffmanBytes对应的二进制的字符串*/
            StringBuilder sbt = new StringBuilder();
            //将byte字节转换为二进制字符串
            for (int i = 0; i < huffmanBytes.length; i++) {
                byte b = huffmanBytes[i];
                // 判断是否是最后一个字节
                boolean flag = (i == huffmanBytes.length-1);
                sbt.append(byteToBitString(!flag,b));
            }
    
            /*2.把字符串按照指定的方式进行霍夫曼解码*/
            /*把Huffman码表进行调换,因为是反向查询*/
            Map<String,Byte> map = new HashMap<>();
            for (Map.Entry<Byte,String> entry:huffmanCode.entrySet()){
                map.put(entry.getValue(),entry.getKey());
            }
    
            /*3.创建集合,存放解码后的byte*/
            List<Byte> byteList = new ArrayList<>();
            /*使用索引不停的扫描stb*/
            for (int k = 0; k < sbt.length();){
                int count = 1;  /*小的计数器,用于判断是否字符串是否在Huffman的码标中*/
                Byte b = null;  /*用于存放编码后的字节*/
                boolean loop = true;
                while (loop){
                    /*k不动,让count进行移动,指定匹配到一个字符*/
                    String key = sbt.substring(k,k+count);
                    b = map.get(key);
                    if (b == null){ //没有匹配到
                        count++;
                    }else {
                        //匹配到就退出循环
                        loop = false;
                    }
                }
                byteList.add(b);
                k += count;  //k直接移动到count在进行下一次遍历
            }
            
            /*4.当for循环结束后,将list中存放的数据放入到byte数组中返回即可*/
            byte[] decodeByteCodes = new byte[byteList.size()];
            for (int j = 0; j < decodeByteCodes.length; j++) {
                decodeByteCodes[j] = byteList.get(j);
            }
            return decodeByteCodes;
        }

    查看解码后的结果:

     完整代码

    package data.structer.tree;
    
    import java.util.*;
    
    public class HuffmanCodeDemo {
    
        static Map<Byte,String> huffmanCode = new HashMap<>();
        static StringBuilder stringBuilder = new StringBuilder();
    
        public static void main(String[] args) {
            String content = "i like like like java do you like a java oh oh oh";
            //System.out.println("原始的长度是:"+content.length());
    
            byte[] bytes = getZip(content.getBytes());
           // System.out.println("Huffman编码后的字符串长度是:"+bytes.length);
            System.out.println("解码后的字符串是:"+new String(decode(huffmanCode,bytes)));
        }
    
        // 解码
    
        /**
         *
         * @param huffmanCode   对应霍夫曼编码表
         * @param huffmanBytes  霍夫曼编码得到的字节数组
         * @return
         */
        public static byte[] decode(Map<Byte,String> huffmanCode,byte[] huffmanBytes){
            StringBuilder sbt = new StringBuilder();
            //将byte字节转换为二进制字符串
            for (int i = 0; i < huffmanBytes.length; i++) {
                byte b = huffmanBytes[i];
                // 判断是否是最后一个字节
                boolean flag = (i == huffmanBytes.length-1);
                sbt.append(byteToBitString(!flag,b));
            }
    
            //把字符串按照指定的方式进行霍夫曼解码
            Map<String,Byte> map = new HashMap<>();
            for (Map.Entry<Byte,String> entry:huffmanCode.entrySet()){
                map.put(entry.getValue(),entry.getKey());
            }
    
            // 创建集合,存放byte
            List<Byte> byteList = new ArrayList<>();
            for (int k = 0; k < sbt.length();){
                int count = 1;
                Byte b = null;
                boolean loop = true;
                while (loop){
                    String key = sbt.substring(k,k+count);
                    b = map.get(key);
                    if (b == null){ //没有匹配到
                        count++;
                    }else {
                        loop = false;
                    }
                }
                byteList.add(b);
                k += count;
            }
    
            byte[] decodeByteCodes = new byte[byteList.size()];
            for (int j = 0; j < decodeByteCodes.length; j++) {
                decodeByteCodes[j] = byteList.get(j);
            }
            return decodeByteCodes;
        }
    
        /**
         * 将一个byte转换成二进制的字符串
         * @param flag 表示是否要进行补高位,如果是true则需要补高位,false则不需要补位,如果是最后一个字节不需要补高位
         * @param b
         * @return  是该byte对应的二进制字符串(补码返回)
         */
        public static String byteToBitString(boolean flag,byte b){
            int temp = b;
            if (flag){
                temp |= 256;
            }
            String str = Integer.toBinaryString(temp);
            if (flag){
                return str.substring(str.length()-8);
            }else {
                return str;
            }
        }
    
    
        public static byte[] getZip(byte[] bytes){
            List<HuffmanNode> nodes = getNodes(bytes);
            // 根据nodes创建赫夫曼树
            HuffmanNode root = createHuffmanTree(nodes);
            // 根据root节点生成霍夫曼编码
            huffmanCode = getCode(root);
            // 根据霍夫曼编码,对数据进行压缩,得到字节数组
            byte[] huffmanCodeBytes = zip(bytes,huffmanCode);
           // System.out.println(Arrays.toString(huffmanCodeBytes));
            return huffmanCodeBytes;
        }
    
        /**
         * 统计字符串中每个字符出现的次数,添加到List中进行返回
         * @param bytes
         * @return
         */
        public static List<HuffmanNode> getNodes(byte[] bytes){
            if (bytes.length == 0 || bytes == null){
                System.out.println("字符串为空,无法进行编码");
                return null;
            }
            List<HuffmanNode> nodes = new ArrayList<>();
            Map<Byte,Integer> counts = new HashMap<>();
    
            for (byte item : bytes) {
               Integer count = counts.get(item);
               if (count == null){ // 说明是第一次
                   counts.put(item,1);
               }else {
                   counts.put(item,count+1);
               }
            }
    
            for (Map.Entry<Byte,Integer> node:counts.entrySet()){
                nodes.add(new HuffmanNode(node.getKey(),node.getValue()));
            }
            return nodes;
        }
    
        /**
         * 使用霍夫曼编码进行压缩
         * @param bytes
         * @param huffmanCode
         * @return
         */
        public static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCode){
            if (bytes.length == 0 || bytes == null){
                System.out.println("字符串为空,无法进行编码");
                return null;
            }
            StringBuilder stb = new StringBuilder();
            for (byte b : bytes) {
                stb.append(huffmanCode.get(b));
            }
            int index = 0;
            int len = 0; // 确定霍夫曼编码的长度
            if (stb.length() % 8 == 0){
                len = stb.length() / 8;
            }else {
                len = stb.length() / 8 + 1;
            }
            byte[] huffmanCodeBytes = new byte[len];
    
            for (int i = 0; i < stb.length();i+=8){
                String strByte = null;
                if (i+8 > stb.length()){ // 不够8位
                    strByte = stb.substring(i);
                }else {
                    strByte = stb.substring(i,i+8);
                }
                huffmanCodeBytes[index] = (byte)Integer.parseInt(strByte,2);
                index++;
            }
            return huffmanCodeBytes;
        }
    
    
        public static void getCode(HuffmanNode node,String code,StringBuilder stringBuilder){
            StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
            stringBuilder2.append(code);
            if (node!=null){
                if (node.data == null){ // 是非叶子节点
                    //向左递归
                    getCode(node.left,"0",stringBuilder2);
                    //向右递归
                    getCode(node.right,"1",stringBuilder2);
                }else {
                    huffmanCode.put(node.data,stringBuilder2.toString());
                }
            }
        }
    
        public static Map<Byte,String> getCode(HuffmanNode root){
            if (root == null){
                System.out.println("没有生成霍夫曼树");
                return null;
            }else {
                getCode(root.left,"0",stringBuilder);
                getCode(root.right,"1",stringBuilder);
            }
            return huffmanCode;
        }
    
        /**
         * 生成霍夫曼树
         * @param nodes
         * @return
         */
        public static HuffmanNode createHuffmanTree(List<HuffmanNode> nodes){
            if (nodes.size() == 0 || nodes == null){
                System.out.println("生成的List为空,不能生成霍夫曼树");
                return null;
            }
            while (nodes.size() > 1){
                Collections.sort(nodes);
    
                HuffmanNode leftNode = nodes.get(0);
                HuffmanNode rightNode = nodes.get(1);
                HuffmanNode parent = new HuffmanNode(null,leftNode.weight+rightNode.weight);
    
                parent.left = leftNode;
                parent.right = rightNode;
    
                nodes.remove(leftNode);
                nodes.remove(rightNode);
                nodes.add(parent);
    
            }
            return nodes.get(0);
        }
    }
    
    
    class HuffmanNode implements Comparable<HuffmanNode>{
        public Byte data;
        public int weight;
        public HuffmanNode left;
        public HuffmanNode right;
    
        public HuffmanNode(Byte data, int weight) {
            this.data = data;
            this.weight = weight;
        }
    
        public HuffmanNode() {
        }
    
        @Override
        public String toString() {
            return "HuffmanNode{" +
                    "data=" + data +
                    ", weight=" + weight +
                    '}';
        }
    
        @Override
        public int compareTo(HuffmanNode o) {
            return this.weight - o.weight;
        }
    
    }
  • 相关阅读:
    python之变量与常量
    Python之历史
    关于图像重采样插值算法
    常用的日期的方法
    数组常用的方法
    判断鼠标进入容器方向
    用JS实现一个时钟的效果
    搞定flex布局
    整理 45 道 CSS 基础面试题(附答案)
    CSS实战3
  • 原文地址:https://www.cnblogs.com/luhuajun/p/12303825.html
Copyright © 2020-2023  润新知