ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

赫夫曼编码相关

2021-01-18 12:29:03  阅读:182  来源: 互联网

标签:Node 编码 stringBuilder 相关 byte 节点 夫曼


1.介绍

赫夫曼编码也翻译为 赫夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式, 属于一种程序算法
赫夫曼编码是赫赫夫曼树在电讯通信中的经典的应用之一。
赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~90%之间
赫夫曼码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,称之为最佳编码
简单来说,赫夫曼编码就是一种利用赫夫曼树的压缩方式,可以将一些本文,字符串等进行压缩
比如字母‘L’再霍夫曼树中的路径为11
意思是从根节点开始找,向左为1 ,向右为0,最后得到路径为11,则这个霍夫曼树大概是这个样子,Ld的位置确定了
在这里插入图片描述

2.具体实现步骤

1.创建赫夫曼树
2.生成赫夫曼编码表(因为霍夫曼编码就是根据霍夫曼树得到的,所以必须现有霍夫曼树才可以)
3.用赫夫曼编码表,将数据进行压缩
下面介绍详细的步骤:


一.创建赫夫曼树


  1. 从小到大进行排序, 将每一个数据,每个数据都是一个节点 , 每个节点可以看成是一颗最简单的二叉树
  2. 取出根节点权值最小的两颗二叉树
  3. 组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
  4. 再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树

注意:
赫夫曼二叉树特点就是权路径最小,所以这里就突出了两个点,一个时权值,一个是这个数据本身的值,且下面的代码,每个节点用list来表示
代码:

创建所需要的节点方法(节点类)

//创建节点
class Node implements Comparable<Node> {
    Byte data;//存放数据本身,比如‘a’=27
    int weight; //表示这个数据出现的次数
    Node left;
    Node right;

    //构造方法
    public Node(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }

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

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

    //为例测试,在这里写一个前序遍历的方法
    public void preOrder() {
        System.out.println(this);
        if (this.left != null) {
            this.left.preOrder();
        }
        if (this.right != null) {
            this.right.preOrder();
        }
    }
}

以下面的字符串为例: i like like like java do you like a java
我们要先根据这个字符串,得到一个byte数组,然后再来得到上述的node节点,然后为了后续操作方便,将这些得到的节点放入一个list集合中存储起来,
至此:我们实现了由一个字符串,得到了一批节点,这些节点就是后来放到赫夫曼树种的节点,下面附上述描述的代码:

 /**
     * 将准备构建赫夫曼树的Node 节点放到 List
     * @param bytes 接收字节数组
     * @return 返回的就是list形式
     */
    private static List<Node> getNodes(byte[] bytes) {
        //新创建一个arrayLIst集合
        ArrayList<Node> nodes = new ArrayList<Node>();
        //遍历bytes,统计每一个byte出现的次数,->map<key,value>
        //这里的原因是因为你一个节点有哪些属性变量呢?
        //最终的两个就是data,和weight,这里用一个map将他们全部得到
        Map<Byte, Integer> counts = new HashMap<>();
        for (byte b : bytes) {
            Integer count = counts.get(b);//统计b在counts中出现的次数
            if (count == null) {
                counts.put(b, 1);
            } else {
                counts.put(b, count + 1);//这里这么写是应为map会对相同的key进行覆盖
            }
        }
        //吧每一个键值对对像Node,放进nodes集合中
        for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
            nodes.add(new Node(entry.getKey(), entry.getValue()));
        }
        return nodes;
    }

这里已经获取全部的node节点,为创建树做准备:
下面创建赫夫曼树:(具体创建原理不做赘述)

    //通过一个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);
            //创建一颗新的霍夫曼树,他没有值,只有权重
            Node parent = new Node(null, leftNode.weight + rightNode.weight);
            parent.left = leftNode;
            parent.right = rightNode;
            //将已经处理的两个二叉树删除
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            nodes.add(parent);
        }
        //nodes最后的节点,即使赫夫曼树的根节点
        return nodes.get(0);
    }

创建完成之后,我们可以用一个前序遍历来看看,这个数的每个节点都是什么,下面附前序遍历的代码:
再创建节点的最后,已经写了前序遍历的代码,这里写的是再主方法种进新测试,(最后会改写成方法,这里不必拘泥写在哪里):

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

这里data不为null,表示的就是他本身的数据,weight表示权重

至此,我们完成了第一步,即创建好了霍夫曼树,下面进入第二步,生成霍夫曼编码:


二.生成霍夫曼编码


 //* 思路:
    //* 1.将赫夫曼编码表存放在map中  形式为:生成的赫夫曼编码表是{32=01, 97=100, 100=11000, 117=11001, 101=1110, 118=11011,
    // 105=101, 121=11010, 106=0010, 107=1111, 108=000, 111=0011}
    static Map<Byte, String> huffmanCodes = new HashMap<>();
    //2.在生成赫夫曼编码表时,要创建路径 ,定义一个StringBuilder 存储某个叶子节点路径
    static StringBuilder stringBuilder = new StringBuilder();
    /**
     * 功能:将传入的所有node节点,得到其霍夫曼编码,并放入haffumanCodes 中
     * @param node          传入节点
     * @param code          路径,左节点时0,右节点是1,
     * @param stringBuilder 用于拼接路径
     */
    private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);//借助于stringBuilder 来生成,生成的code会继续加入到string builder2 中
        //将code加入到string builder2 中
        stringBuilder2.append(code);
        if (node != null) {//如果node不为空,不处理,否则要判断他是不是叶子节点
            //判断当前节点是不是叶子节点
            if (node.data == null) {//非叶子节点,向左递归
                getCodes(node.left, "0", stringBuilder2);
                //向右递归
                getCodes(node.right, "1", stringBuilder2);
            } else {//是叶子节点
                //表示找到了某个叶子节点的最后
                huffmanCodes.put(node.data, stringBuilder2.toString());
            }
        }
    }
    //为了调用方便,重载getCode
    private static Map<Byte, String> getCodes(Node root) {
        if (root == null) {
            return null;
        } else {
            //处理左子树
            getCodes(root.left, "0", stringBuilder);
            //处理右子树
            getCodes(root.right, "1", stringBuilder);
        }
        return huffmanCodes;
    }

经过上述;我们已经生成了赫夫曼编码表:
在这里插入图片描述
下面用这个表进行压缩:


三:利用赫夫曼编码表进行压缩


    //编写一个方法,将字符串对应的byte[]数组,通过生成的哈夫曼编码表,返回一个赫夫曼编码,压缩有的byte[]

    /**
     * @param bytes        这是原始数组对应的byte[] 有a b c ....
     * @param huffmanCodes 生成的赫夫曼编码map,其中的内容时每个对应的字母,对应了哪一个字符串
     * @return 返回赫夫曼编码后的byte[]
     */
    private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
        //1.利用huffmanCodes,将bytes转成  赫夫曼编码对应的字符串
        StringBuilder stringBuilder = new StringBuilder();
        //2.遍历byte[]数组
        for (byte b : bytes) {
            stringBuilder.append(huffmanCodes.get(b));
        }
        System.out.println(stringBuilder.toString());
        //将101011100111111。。。。。。。。。转成字符串
        //统计返回byte[]huffmancode的长度
        int len;
        if (stringBuilder.length() % 8 == 0) {
            len = stringBuilder.length() / 8;
        } else {
            len = stringBuilder.length() / 8 + 1;
        }
        //创建一个存储压缩有的byte数组
        byte[] huffmancodeBytes = new byte[len];
        //记录是第几个byte
        int index = 0;//表示霍夫曼编码对应的字符串长度
        for (int i = 0; i < stringBuilder.length(); i += 8) {//因为每八位对应一个byte,
            String strBytes;
            if (i + 8 > stringBuilder.length()) {
                strBytes = stringBuilder.substring(i);//直接截取
            } else {
                strBytes = stringBuilder.substring(i, i + 8);//截取八位
            }
            //将strByte 转成一个byte,梵高huffmancodeBytes中
            huffmancodeBytes[index] = (byte) Integer.parseInt(strBytes, 2);//以二进制形式转换
            index++;
        }
        //for循环结束后,就能拿到赫夫曼编码对应的字节数组,将其返回
        return huffmancodeBytes;
    }

这个压缩的方法要注意:比如之前的字符串“l like like like java ”,利用霍夫曼编码对应后,生成的一串编码为:1110101011101011101001110011010101000101
此刻要将这串编码,按照八个byte一个字节进行压缩,生成一个数字,这里得到的结果是:
在这里插入图片描述
即最开始的一个字符串给压缩成了一个字符数组,实现了 压缩,至于怎么把他变回去,关系到霍夫曼解码,又是另外一个故事了。。。。。。

标签:Node,编码,stringBuilder,相关,byte,节点,夫曼
来源: https://blog.csdn.net/m0_48112568/article/details/112768143

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有