赫夫曼编码

1、基本介绍

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

2、原理剖析

通信领域中信息的处理方式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
将对应的码数转换为二进制为:
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-变长编码

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-赫夫曼编码

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 // 各个字符对应出现的个数

按照上面字符出现的次数构建一颗赫夫曼树, 次数作为权值.(图后)
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

3、代码实现数据压缩

字符串压缩

将给出的一段文本,比如 “i like like like java do you like a java” , 根据前面的讲的赫夫曼编码原理,对其进行数据压缩处理 ,形式如 "1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110
"

思路分析

  1. Node { data (存放数据), weight (权值), left 和 right }
  2. 得到 “i like like like java do you like a java” 对应的 byte[] 数组
  3. 编写一个方法,将准备构建赫夫曼树的Node 节点放到 List , 形式 [Node[date=97 ,weight = 5], Node[]date=32,weight = 9]…], 体现 d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9
  4. 可以通过List 创建对应的赫夫曼树

代码实现

package com.imagpie.tree.huffmancode;

import java.util.*;

/**
 * 赫夫曼编码
 */
public class HuffmanCode {
    public static void main(String[] args) {
        String str = "i like like like java do you like a java";
        byte[] contentBytes = str.getBytes();
        System.out.println(Arrays.toString(contentBytes));
        byte[] huffmanCodesBytes = huffmanZip(contentBytes);
        System.out.println(Arrays.toString(huffmanCodesBytes));
    }


    //用一个方法将前面的方法封装起来,便于调用

    /**
     * @param bytes 原始的字符串对应的字节数组
     * @return 经过赫夫曼编码处理后的数组(压缩后的数组)
     */
    private static byte[] huffmanZip(byte[] bytes) {
        List<Node> nodes = getNodes(bytes);
        //根据nodes集合创建赫夫曼树
        Node huffmanTree = createHuffmanTree(nodes);
        //生成对应的赫夫曼编码,根据赫夫曼树创建对应的赫夫曼编码
        getCodes(huffmanTree, "", stringBuilder);

        //根据赫夫曼编码进行压缩,并且得到压缩后的字节数组
        return zip(bytes, huffmanCodes);
    }


    /**
     * 将字符串对应的byte[]数组,通过生成的赫夫曼编码表返回一个赫夫曼编码表压缩后的byte[]
     *
     * @param bytes        bytes这是原始的字符串对应的byte[]
     * @param huffmanCodes 生成的赫夫曼编码表
     * @return 返回赫夫曼编码处理后的byte数组
     */
    private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
        StringBuilder stringBuilder = new StringBuilder();
        for (byte b : bytes) {
            stringBuilder.append(huffmanCodes.get(b));
        }
        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;
        //因为是没8位对应一个byte,所以步长应该是8
        for (int i = 0; i < stringBuilder.length(); i += 8) {
            String strByte;
            if (i + 8 > stringBuilder.length()) {
                strByte = stringBuilder.substring(i);
            } else {
                strByte = stringBuilder.substring(i, i + 8);
                //将strByte转成一个byte,放入到huffmanCodes中
                huffmanCodeBytes[index] = (byte) Integer.parseInt(strByte, 2);
                //指针向后移动
                index++;
            }
        }
        return huffmanCodeBytes;
    }


    //1、将赫夫曼编码表存放在map中
    //例如:32-》01    97——》100
    static Map<Byte, String> huffmanCodes = new HashMap<>();
    //2、生成赫夫曼编码表示,需要去拼接路径,定义一个StringBuilder,存储某个叶子结点的路径
    static StringBuilder stringBuilder = new StringBuilder();

    /**
     * 功能:将传入的node结点的所有叶子结点的赫夫曼编码,并放如到map集合中
     *
     * @param node          传入的结点
     * @param code          路径的值 左子结点为0,右子结点为1
     * @param stringBuilder 用于拼接路径
     */
    private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
        StringBuilder stringBuffer2 = new StringBuilder(stringBuilder);
        stringBuffer2.append(code);
        if (node != null) {
            //如果node结点==空则不处理
            //判断当前node是叶子结点还是非叶子结点
            if (node.data == null) {
                //当节点的data为null的话,表示为非叶子结点
                //进行递归处理
                getCodes(node.left, "0", stringBuffer2);
                getCodes(node.right, "1", stringBuffer2);
            } else {
                //当结点的data不为空,则表示该结点为叶子结点,路径拼接就可以结束了
                huffmanCodes.put(node.data, stringBuffer2.toString());
            }
        }
    }


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

    }


    private static List<Node> getNodes(byte[] bytes) {
        ArrayList<Node> nodes = new ArrayList<>();
        Map<Byte, Integer> counts = new HashMap<>();
        //用于统计字符重复的次数
        for (byte b : bytes) {
            Integer count = counts.get(b);
            if (count == null) {
                counts.put(b, 1);
            } else {
                counts.put(b, count + 1);
            }
        }
        //将每个键值对转为Node对象,并且加入集合
        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);
            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);
        }
        //返回最后的赫夫曼树的根节点
        return nodes.get(0);
    }
}

//创建Node,包含数据和权值
class Node implements Comparable<Node> {
    Byte data;//存放字符的十进制形式
    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();
        }
    }


}

4、数据解压

解压刚刚压缩的字符串

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值