栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

哈夫曼编码详解

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

哈夫曼编码详解

一:基本介绍

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

1.1:原理剖析

        1:通信领域中信息的处理方式1-定长编码

将 i like like like java do you like a java 定长编码       // 共40个字符(包括空格)  

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  //对应Ascii码

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:  通信领域中信息的处理方式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...  

字符的编码都不能是其他字符编码的前缀,符合此要求的编码叫做前缀编码, 即不能匹配到重复的编码,例如 1=a,110=o,那么a是o的前缀所以该编码不是前缀编码

        3:通信领域中信息的处理方式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  // 各个字符对应的个数 按照上面字符出现的次数构建一颗赫夫曼树, 次数作为权值.

根据赫夫曼树,给各个字符规定编码 (前缀编码), 向左的路径为0 ,向右的路径为1 

编码如下: o: 1000   u: 10010  d: 100110  y: 100111  i: 101 a : 110     k: 1110    e: 1111       j: 0000       v: 0001 l: 001          : 01

按照上面的赫夫曼编码,我们的"i like like like java do you like a java"   字符串对应的编码为 (注意这里我们使用的无损压缩) 1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110

长度为 : 133

说明: 原来长度是  359 , 压缩了  (359-133) / 359 = 62.9% 此编码满足前缀编码, 即字符的编码都不能是其他字符编码的前缀。不会造成匹配的多义性

注意, 这个赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是wpl 是一样的,都是最小的,字符总长度相同。 比如: 我们让每次生成的新的二叉树总是排在权值相同的二叉树的最后一个和最前面

二:案例——哈夫曼压缩

功能: 根据赫夫曼编码压缩数据的原理,需要创建 "i like like like java do you like a java" 对应的赫夫曼树

思路: (1) CodeNode{ data (存放数据), weight (权值), left  和 right }                                               (2) 得到  "i like like like java do you like a java"   对应的 byte[] 数组                                             (3)  编写一个方法,将准备构建赫夫曼树的Node 节点放到 List  , 形式 [CodeNode[date=97 ,weight = 5], CodeNode[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 创建对应的赫夫曼树                                                                                          (5)根据哈夫曼树生成哈夫曼编码表,左路径为0,右路径为1                                                      (6)将转换后的字符串转换为字节数组

package com.atgguigu.huffmanTree;

import java.util.*;

public class HuffmanCode {
    static Map hCode = new HashMap<>();  //存储字符对应编码
    static StringBuilder stringBuilder = new StringBuilder();   //拼接路径生成编码

    public static void main(String[] args) {
        String s = "i like like like java do you like a java";
        byte[] bytes = s.getBytes();
        System.out.println(Arrays.toString(bytes));
        System.out.println("没有压缩的原长度:"+bytes.length);

        byte[] zip = zip(s);
        System.out.println(Arrays.toString(zip));
        System.out.println("哈夫曼编码压缩后的长度:"+zip.length);


    }
    
    private static byte[] zip(String s){
        byte[] bytes = s.getBytes(); //获取字节数组
        List nodes = getNodes(bytes);  //构建集合存储字符和权值
        CodeNode huffmanTree = createHuffmanTree(nodes);  //构建哈夫曼树
        getCode(huffmanTree);  //根据哈夫曼树获取对应编码表

        byte[] zip = zip(bytes, hCode);  //将字节数组转化为哈夫曼编码字节数组
        return zip;
    }

    
    private static List getNodes(byte[] bytes){
        ArrayList nodes = new ArrayList<>();
        Map map = new HashMap<>();

        //统计每个字符数量
        for (byte b : bytes) {
            Integer count = map.get(b);
            if(count == null){
                map.put(b,1);
            }else {
                map.put(b,count+1);
            }
        }
        //构建CodeNode对象
        for(Map.Entry entry: map.entrySet()){
            nodes.add(new CodeNode(entry.getKey(),entry.getValue()));
        }
        return  nodes;
    }

    //创建哈夫曼树
    private static CodeNode createHuffmanTree(List codeNodes){

        while (codeNodes.size() > 1){
            Collections.sort(codeNodes);

            CodeNode leftNode = codeNodes.get(0);
            CodeNode rightNode = codeNodes.get(1);

            CodeNode parent = new CodeNode(leftNode.weight+rightNode.weight);
            parent.left = leftNode;
            parent.right = rightNode;

            codeNodes.remove(leftNode);
            codeNodes.remove(rightNode);

            codeNodes.add(parent);
        }
        return codeNodes.get(0);
    }

    //前序遍历
    public static void preOrder(CodeNode node){
        if(node != null){
            node.preOrder();
        }else {
            System.out.println("你玩我?空树还传过来");
        }
    }

    //哈夫曼编码表

    
    private static  void getCode(CodeNode node, String code, StringBuilder s){
        StringBuilder s2 = new StringBuilder(s);
        s2.append(code);
        if(node != null){
            if(node.data == null){ //非叶子结点
                getCode(node.left, "0",s2);  //左递归
                getCode(node.right, "1",s2);  //左递归
            }else { //叶子结点
                hCode.put(node.data,s2.toString());
            }
        }
    }

    private static  Map getCode(CodeNode node){
        if (node == null){
            return null;
        }
        getCode(node.left,"0",stringBuilder);
        getCode(node.right,"1",stringBuilder);
        return hCode;
    }

    //压缩生成哈夫曼编码
    
    private static byte[] zip(byte[] bytes,Map hCode){
        StringBuilder stringBuilder = new StringBuilder();
        for (byte b : bytes) {
            stringBuilder.append(hCode.get(b));
        }

        //统计byte[] 的长度
        int len ;
        if(stringBuilder.length() % 8 == 0){
            len =stringBuilder.length() / 8;
        }else {
            len =stringBuilder.length() / 8 + 1;
        }

        byte[] bs = new byte[len];
        int index = 0;
        for (int i =0;i stringBuilder.length()-1){
                strByte = stringBuilder.substring(i);
            }else {
                strByte = stringBuilder.substring(i,i+8);
            }
            bs[index] = (byte) Integer.parseInt(strByte,2);
            index++;
        }

        return bs;
    }


}


class CodeNode implements Comparable{
    Byte data; //存放数据(字符)
    int weight; //权值
    CodeNode left;
    CodeNode right;

    public CodeNode( int weight) {
        this.weight = weight;
    }

    public CodeNode(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }

    @Override
    public int compareTo(CodeNode o) {
        return this.weight - o.weight;  //小到大排序
    }

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

    //前序遍历
    public void preOrder(){
        System.out.print("==>"+this);

        if(this.left != null){
            this.left.preOrder();
        }

        if(this.right != null){
            this.right.preOrder();
        }
    }

}
二:案例——哈夫曼解压

 

package com.atgguigu.huffmanTree;

import java.util.*;

public class HuffmanCode {
    static Map hCode = new HashMap<>();  //存储字符对应编码
    static StringBuilder stringBuilder = new StringBuilder();   //拼接路径生成编码

    public static void main(String[] args) {
        String s = "i like like like java do you like a java";
        byte[] bytes = s.getBytes();
        System.out.println(Arrays.toString(bytes));
        System.out.println("没有压缩的原长度:"+bytes.length);

        byte[] zip = zip(s);
        System.out.println(Arrays.toString(zip));
        System.out.println("哈夫曼编码压缩后的长度:"+zip.length);

        byte[] decode = decode(hCode, zip);
        System.out.println("解压:"+new String(decode));

    }
 
    
    private static String byteToBitString(boolean flag, byte b) {
        //使用变量保存 b
        int temp = b; //将 b 转成 int
        //如果是正数我们还存在补高位
        if(flag) {
            temp |= 256; //按位与 256  1 0000 0000  | 0000 0001 => 1 0000 0001
        }
        String str = Integer.toBinaryString(temp); //返回的是temp对应的二进制的补码
        if(flag) {
            return str.substring(str.length() - 8);
        } else {
            return str;
        }
    }

    //编写一个方法,完成对压缩数据的解码
    
    private static byte[] decode(Map huffmanCodes, byte[] huffmanBytes) {

        //1. 先得到 huffmanBytes 对应的 二进制的字符串 , 形式 1010100010111...
        StringBuilder stringBuilder = new StringBuilder();
        //将byte数组转成二进制的字符串
        for(int i = 0; i < huffmanBytes.length; i++) {
            byte b = huffmanBytes[i];
            //判断是不是最后一个字节
            boolean flag = (i == huffmanBytes.length - 1);
            stringBuilder.append(byteToBitString(!flag, b));
        }
        //把字符串安装指定的赫夫曼编码进行解码
        //把赫夫曼编码表进行调换,因为反向查询 a->100 100->a
        Map  map = new HashMap();
        for(Map.Entry entry: huffmanCodes.entrySet()) {
            map.put(entry.getValue(), entry.getKey());
        }

        //创建要给集合,存放byte
        List list = new ArrayList<>();
        //i 可以理解成就是索引,扫描 stringBuilder
        for(int  i = 0; i < stringBuilder.length(); ) {
            int count = 1; // 小的计数器
            boolean flag = true;
            Byte b = null;

            while(flag) {
                //1010100010111...
                //递增的取出 key 1
                String key = stringBuilder.substring(i, i+count);//i 不动,让count移动,指定匹配到一个字符
                b = map.get(key);
                if(b == null) {//说明没有匹配到
                    count++;
                }else {
                    //匹配到
                    flag = false;
                }
            }
            list.add(b);
            i += count;//i 直接移动到 count
        }
        //当for循环结束后,我们list中就存放了所有的字符  "i like like like java do you like a java"
        //把list 中的数据放入到byte[] 并返回
        byte b[] = new byte[list.size()];
        for(int i = 0;i < b.length; i++) {
            b[i] = list.get(i);
        }
        return b;
    }

}


class CodeNode implements Comparable{
    Byte data; //存放数据(字符)
    int weight; //权值
    CodeNode left;
    CodeNode right;

    public CodeNode( int weight) {
        this.weight = weight;
    }

    public CodeNode(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }

    @Override
    public int compareTo(CodeNode o) {
        return this.weight - o.weight;  //小到大排序
    }

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

    //前序遍历
    public void preOrder(){
        System.out.print("==>"+this);

        if(this.left != null){
            this.left.preOrder();
        }

        if(this.right != null){
            this.right.preOrder();
        }
    }

}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/358032.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号