树结构入门教程-赫夫曼编码

2020-03-23  本文已影响0人  会上树的程序猿

前面我们学习了赫夫曼树的创建的过程,关于创建的详细过程可以看看上节的原理分析和代码实现,这节我们在它的基础上来学习赫夫曼的实践------------->编码过程,首先我们先来介绍下什么是赫夫曼编码.

赫夫曼编码介绍

赫夫曼编码也称(Huffman Code)哈夫曼编码,是一种常见的编码方式.

赫夫曼编码广泛的用于数据文件压缩,一般其压缩率达到20% -90%之间

赫夫曼编码是可变成编码的其中一种,由赫夫曼本人于1952年提出的一种编码方式,被称之为最佳编码.

接下来我们通过具体的案例来实现编码的过程:

假设我有一串字符串如: String string = "i like like like java do you like a java",我可以手动统计统计这个字符串是有40个字符(其中包括空格),我们可以分别统计每一个字符的个数如下:

上述就是每一个字符的出现的个数,其中空格为9个,接下来我们利用上述的这些首先来构建一棵赫夫曼树,其中我们以每个字符的个数作为权值,具体的步骤这里就不重复了,上节我们我们已经说了,我们直接来看最终构建成的赫夫曼树如下图:

赫夫曼树.png

这其中我们规定赫夫曼树向左的路径为0,向右的路径规定为1,也就是如上图所示,我们可以通过规定的路径为每一个字符进行编码操作如下所示:

这里我们获取到了每一个字符的编码,我们可以按照上述编码规则那么我们的字符串String string = "i like like like java do you like a java"对应的编码应该为如下所示:

1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100

最后将上述编码进行字节的转换,进行传输即可,这就是整个赫夫曼编码的过程,接着我们利用代码的方式来实现

代码实现

//创建node,这里是带数据和权值
class Node implements Comparable<Node>{
Byte data; //用来存放数据本身(字符),比如:字符'a' -->97 ' '--->32
int weight; //权值,也就是我们字符出现的次数
Node left;
Node right;

//前序遍历的方法
public void preOrder(){
    System.out.println(this);
    if (this.left !=null){
     this.left.preOrder();
    }
    if (this.right !=null){
        this.right.preOrder();
    }
}
public Node(Byte data, int weight) {
    this.data = data;
    this.weight = weight;
}

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

@Override
public int compareTo(Node o) {
    return this.weight -o.weight;
}
String string = "i like like like java do you like a java";
byte[] bytes = string.getBytes();
 public static List<Node> getNodes(byte[] bytes){
    //1.创建list来
    ArrayList<Node> nodes = new ArrayList<>();
    //创建map,主要是用来统计byte出现的次数
    HashMap<Byte, Object> map = new HashMap<>();
    for (byte b:bytes){
        Integer count = (Integer) map.get(b);
        if (count ==null){
            map.put(b,1);
        }else {
            map.put(b,count+1);
        }
    }
    //遍历map,将键值对转成node节点对象
    for (Map.Entry<Byte,Object> entry:map.entrySet()){
        nodes.add(new Node(entry.getKey(), (Integer) entry.getValue()));
    }
    return nodes;
}

来看测试代码,如下:

/**
 * 赫夫曼编码
 *
 */
public class HuffmanCode {
public static void main(String[] args) {
    String string = "i like like like java do you like a java";
    byte[] bytes = string.getBytes();
    System.out.println(bytes.length);
    List<Node> nodes = getNodes(bytes);
    System.out.println(nodes);

测试结果如下图所示:

节点测试图.png

太长了,这里就截了一部分,我们接着看拿到了List中存储的节点,我们可以利用它完成赫夫曼树的创建,代码如下:

//创建赫夫曼树
public static Node createHuffmanTree(List<Node> nodes){
    //循环处理
    while (nodes.size() >1){
        //1.首先我们来排序(从小到大)
        Collections.sort(nodes);
        //2.取出第一棵最小的二叉树节点
        Node leftNode = nodes.get(0);
        //3.取出第二棵较小的二叉树节点
        Node rightNode = nodes.get(1);
        //4.创建一棵新的二叉树,这课新的二叉树是没有根节点的也就是我们的data为null,只有权值
        Node parent = new Node(null, leftNode.weight + rightNode.weight);
        //4.1.设置parent的左右节点
        parent.left = leftNode;
        parent.right = rightNode;
        //4.2.将处理过的二叉树节点从nodes中删除,便于后面二叉树节点的操作
        nodes.remove(leftNode);
        nodes.remove(rightNode);
        //4.3.将parent加入到nodes中作为下一次操作
        nodes.add(parent);
    }
    //nodes中最后剩余的节点是我们要的赫夫曼的根节点
    return nodes.get(0);
}

来看测试结果,如下图所示:

赫夫曼树的创建.png

可以对比我们之前分析的那个图来看我们创建的这颗赫夫曼树,到这里我们的工作完成了一半了,接下来我们首先需要做这个事情

  //1.我们将赫夫曼编码存放在map中
 static Map<Byte,String> huffmanCodes =  new HashMap<Byte,String>();
//2.在生成赫夫曼编码表时,我们需要去拼接路径,也就是用来记录某一个叶子节点的路径.这里选择StringBuilder来操作
 static StringBuilder sb = new StringBuilder();

这里我们准备好我们所需要的的容器,接着进行核心代码的编写:

/**
 * 目的 :将传入的node节点的所有叶子节点的赫夫曼编码得到,并存放到huffmanCodes
 * @param node 传入的节点
 * @param code 表示节点的路径 0表示左子节点 1:表示右子节点
 * @param sb 用来拼接我们叶子节点的路径
 */
private static void getHuffmanCodes(Node node,String code,StringBuilder sb){
    //初始化新的StringBuilder
    StringBuilder builder = new StringBuilder(sb);
    //拼接当前这个coed
    builder.append(code);
    //处理节点
    if (node !=null){ //如果node== null 我们不做处理
        //判断当前节点是叶子节点还是飞叶子节点
        if (node.data ==null){ //表示非叶子节点
            //递归处理
            //说明:如果是向左的话,递归处理
            getHuffmanCodes(node.left,"0",builder);
            //如果是向右的话,递归处理
            getHuffmanCodes(node.right,"1",builder);
        }else { //表示叶子节点
            //表示我们已经到了某叶子节点的最后,记录它的路径
            huffmanCodes.put(node.data,builder.toString());
        }
    }
}

我们来看测试代码:

  //测试赫夫曼树是否生成对应的赫夫曼编码
    Map<Byte, String> huffmanCodes = getHuffmanCodes(root);
    System.out.println("赫夫曼编码表:"+ huffmanCodes);

测试结果如下:

赫夫曼编码表.png

我们将赫夫曼树转成对应的赫夫曼编码之后,到这里我们的工作快接近尾声了,还有最后一步就是我们将上述生成的编码压缩成字节数组,代码如下:

 /**
 *
 * @param bytes 原始字符串所对应的byte数组
 * @param huffmanCodes 生成赫夫曼编码的map
 * @return
 */
private static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCodes){
    //首先我们利用huffmanCodes将bytes转成赫夫曼编码对应的字符串
    StringBuilder sb = new StringBuilder();
    //遍历bytes
    for (byte b :bytes){
        sb.append(huffmanCodes.get(b));
    }
    System.out.println(sb);
    // 1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100
    //将上述字符串转成byte数组
    //首先我们需要统计返回byte[] huffmanCodeBytes的长度
    int length;
    if (sb.length() %8 ==0){
        length = sb.length() /8;
    }else {
        length = sb.length() /8 +1;
    }
    //创建存储压缩后的byte数组
    byte[] huffmanCodeBytes = new byte[length];
    //创建一个记录遍历的sb的第几个byte
    int index = 0;
    //创建一个临时存储我们遍历到的byte
    String strByte;
    for (int i = 0; i <sb.length() ; i+=8) { //每8为对应一个byte,因此步长为8
        if (i +8 >sb.length()){ //不够8位
            strByte = sb.substring(i);
        }else {
            strByte = sb.substring(i,i+8);
        }
        //2.将strByte转成一个byte并且放入到huffmanCodeBytes中
        huffmanCodeBytes[index] = (byte)Integer.parseInt(strByte,2);
        index ++;
    }
    return huffmanCodeBytes;

}

上述过程涉及到了一些二进制的高位补码和转码的知识,感兴趣的可以自己去看看这块东西,我们来看下测试结果,直接在main方法里调用该方法即可:

字节压缩.png

那么到这里我们所有的工作就完成了,当然我们在实际的生产中也是通过字节来处理我们的数据的,那么关于赫夫曼编码的学习就到这里了,下节我们学习解码的操作,想看代码的去我git地址:

上一篇 下一篇

猜你喜欢

热点阅读