哈夫曼编码详解

一:基本介绍

        哈夫曼编码也翻译为    赫夫曼编码(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<Byte,String> 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<CodeNode> nodes = getNodes(bytes);  //构建集合存储字符和权值
        CodeNode huffmanTree = createHuffmanTree(nodes);  //构建哈夫曼树
        getCode(huffmanTree);  //根据哈夫曼树获取对应编码表

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

    /**
     * 统计字符数目构建对象列表
     * @param bytes
     * @return
     */
    private static List<CodeNode> getNodes(byte[] bytes){
        ArrayList<CodeNode> nodes = new ArrayList<>();
        Map<Byte,Integer> 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<Byte,Integer> entry: map.entrySet()){
            nodes.add(new CodeNode(entry.getKey(),entry.getValue()));
        }
        return  nodes;
    }

    //创建哈夫曼树
    private static CodeNode createHuffmanTree(List<CodeNode> 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("你玩我?空树还传过来");
        }
    }

    //哈夫曼编码表

    /**
     *
     * @param node 节点
     * @param code  路径
     * @param s  拼接路径
     */
    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<Byte,String> getCode(CodeNode node){
        if (node == null){
            return null;
        }
        getCode(node.left,"0",stringBuilder);
        getCode(node.right,"1",stringBuilder);
        return hCode;
    }

    //压缩生成哈夫曼编码
    /**
     *
     * @param bytes 这时原始的字符串对应的 byte[]
     * @param hCode 生成的赫夫曼编码map
     * @return 返回赫夫曼编码处理后的 byte[]
     * 举例: String content = "i like like like java do you like a java"; =》 byte[] contentBytes = content.getBytes();
     * 返回的是 字符串 "1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100"
     * => 对应的 byte[] huffmanCodeBytes  ,即 8位对应一个 byte,放入到 huffmanCodeBytes
     * huffmanCodeBytes[0] =  10101000(补码) => byte  [推导  10101000=> 10101000 - 1 => 10100111(反码)=> 11011000= -88 ] 第一个1代表符号-后面反码
     * huffmanCodeBytes[1] = -88
     */
    private static byte[] zip(byte[] bytes,Map<Byte,String> 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();i+=8){
            String strByte;
            if(i+8 > 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<CodeNode>{
    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<Byte,String> 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));

    }
 
    /**解压
     * 将一个byte 转成一个二进制的字符串, 如果看不懂,可以参考我讲的Java基础 二进制的原码,反码,补码
     * @param b 传入的 byte
     * @param flag 标志是否需要补高位如果是true ,表示需要补高位,如果是false表示不补, 如果是最后一个字节,无需补高位
     * @return 是该b 对应的二进制的字符串,(注意是按补码返回)
     */
    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;
        }
    }

    //编写一个方法,完成对压缩数据的解码
    /**
     *解压
     * @param huffmanCodes 赫夫曼编码表 map
     * @param huffmanBytes 赫夫曼编码得到的字节数组
     * @return 就是原来的字符串对应的数组
     */
    private static byte[] decode(Map<Byte,String> 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<String, Byte>  map = new HashMap<String,Byte>();
        for(Map.Entry<Byte, String> entry: huffmanCodes.entrySet()) {
            map.put(entry.getValue(), entry.getKey());
        }

        //创建要给集合,存放byte
        List<Byte> 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<CodeNode>{
    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();
        }
    }

}
  • 10
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 哈编码是一种使用变长编码来减少数据传输量的有效方法。在 Verilog 中,我们可以通过以下步骤实现哈编码。 首先,我们需要构建哈编码树。这棵树是由输入数据的频率构建而成的。可以使用哈树构建算法,该算法会根据输入数据的频率构造出最优的哈编码树。 然后,我们需要根据构建好的哈编码树生成对应的哈编码表。哈编码表将字符与对应的二进制编码一一对应起来。可以使用深度优先搜索的方法遍历哈编码树,生成哈编码表。 接下来,我们可以编写 Verilog 代码来实现哈编码。首先,我们需要定义输入数据的接口,并接收输入数据。然后,根据输入的字符,查询哈编码表,将对应的二进制编码输出。 最后,我们需要将输出的二进制编码进行传输。为了正确传输数据,我们需要在输出数据前加入标志位,表示输出数据的开始位置。在接收端,需要识别这个标志位,并将后续的二进制编码解码为对应的字符。 总结来说,哈编码在 Verilog 中的实现需要构建哈编码树,生成哈编码表,并编写相应的编码和解码逻辑。这样可以实现对输入数据的高效压缩和解压缩。 ### 回答2: 哈编码是一种基于字符频率来构建编码的最优前缀编码方法。在Verilog中实现哈编码可以分为两步:构建哈树和生成编码表。 首先,构建哈树。我们可以使用二叉树的数据结构来表示哈树。在Verilog中,可以通过定义一个节点结构体来表示二叉树节点,其中包括字符和频率信息,以及左右子节点指针。通过比较字符频率来构建哈树,可以采用贪心算法,每次选择频率最小的两个节点合并为一个新节点,直到只剩下一个节点为止。 接下来,生成编码表。通过遍历哈树,可以得到每个字符的编码。在Verilog中,可以使用递归或者迭代的方式进行树的遍历。当遍历到叶子节点时,记录下路径上的0和1,即可得到每个字符的哈编码。可以使用一个数据结构来保存字符与编码的对应关系,比如使用一个二维数组或者哈希表。 最后,将哈编码应用于实际数据压缩或传输中。通过将原始数据按照对应的编码进行替换或者添加额外的标识,可以实现压缩和解压缩的功能。 总之,通过Verilog实现哈编码需要先构建哈树,然后生成编码表,最后将编码应用于数据压缩或传输中。这是一个相对复杂的任务,需要熟悉Verilog语言和数据结构的相关知识才能完成。 ### 回答3: 哈编码是一种变长编码的压缩算法,常用于将频率较高的字符用较短的编码表示,从而减小存储或传输的数据量。为了实现哈编码,可以使用硬件描述语言Verilog来设计相应的电路。 实现哈编码的Verilog电路需要以下几个主要模块: 1. 频率统计模块:用于统计输入文本中各个字符的频率。输入文本可以通过数据输入端口传入,使用计数器来统计每个字符出现的次数。 2. 构建哈树模块:根据字符频率构建哈树。使用二叉堆等数据结构来优化树的构建过程,按照频率大小进行排序。 3. 哈编码生成模块:根据构建好的哈树,生成每个字符对应的哈编码串。可以使用递归或者迭代的方式遍历哈树,同时记录每个字符的编码。 4. 编码器模块:将输入的文本按照生成的哈编码进行编码。通过读取输入文本的每个字符,并查找对应的哈编码,输出对应的编码串。 5. 译码器模块:将编码后的二进制串按照哈编码进行译码,得到原始文本。根据哈编码树进行译码,从根节点开始依次查找对应的字符。 以上模块可以通过组合逻辑电路来实现,可以利用状态机等技术进行控制。此外,还需要提供测试模块,用于验证设计的正确性。 总的来说,通过使用Verilog语言来设计实现哈编码的电路,可以实现对输入文本进行压缩和解压缩的功能。这种硬件实现可以提高编码运行速度,并减小对系统资源的占用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值