Huffman编码和解码

Posted luhuajun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
    }

}

 

以上是关于Huffman编码和解码的主要内容,如果未能解决你的问题,请参考以下文章

Huffman编码和解码

搬家] 关于 Huffman 编码

Huffman树及其编解码

基于Huffman编码的文件压缩项目

基于Huffman编码的文件压缩项目

h5房卡源码C语言实现哈夫曼树编码解码及问题总结