尚硅谷课程:数据结构与算法中哈夫曼编码的解码问题

文章讨论了解码单个byte时可能出现的译码错误,特别是最后一个byte的处理问题。提出使用静态属性count记录非最后一个byte元素的前导零,以及对最后一个byte特殊处理的方法。最后强调测试时需注意编码时的特殊情况以确保正确译码。

1 问题描述

1.1 问题位置

将单个byte解码为对应码字的处理方式上的问题

1.2 具体问题列举

  1. 解码时,最后一个byte元素的处理问题:如果像韩老师那样是最后一位byte,即不补高位,也不截取直接转换为二进制会出现译码错误的问题.比如 最后一位byte如果是2 那作为接收方我们无法得知 原来的编码到底是 10 还是 010 还是0010...如果这样处理会导致比较严重的译码错误.
  2. 梳理除最后一个元素的其他元素处理的思路:对于其余元素来说,由于使用的Integer.toBinaryString()方法会直接返回正数本身,因此我们提前为其补零.那么为什么要用256或运算来补呢?这是因为256的二进制是1 0000 0000 因此,作为一个正数,与其或运算再截取后八位不会改变其值.对于负数,与其或运算再截取也不会改变值,故我们直接统一:直接把不是最后一个元素的byte都来或上256 转换为二进制后再截取后八位 就可以了.

2 解决思路

2.1对于最后一个byte元素

思路

为了能够感知到最后一位的byte到底是由什么样的码字编码而来的,我们增加静态属性count来记录编码时最后一位的byte被转换之前的码字的前导零的个数  比如 生成到了最后一个发现byte 还剩余码字0011100,我们从i处循环记录0的个数,首次遇到非零就退出不在计数即可

[注意]此时还要修改编码生成byte[] 的方法,对于最后刚好够8位的情况我们也要统计一下前导零的个数,比如若最后是剩00011100也属于难以译码的范围,也需要count来记录,否则我们一译码就成了11100,导致出现错误

实现

bytesToStringBitCode方法

/**
     * TODO 把接收到的bytes[] 哈夫曼编码的byte数组 还原成 一位一位的字符串码
     *
     * @param b 哈夫曼编码的byte数组中的某个元素
     * @return b对应的哈夫曼编码
     */
    public static String bytesToStringBitCode(byte b, boolean isLast) {
        int temp = b;
        //只要不是最后一个我就让你和256或一下,对于负数,或完是原封不动的,对于正数,或完可以补领,
        // 至于多出的一位我们可以通过截取来避免改变正数.
        /*
            总结:由于后序会截断 只取后八个
                故 或上256
                    对于负数没有影响
                    对于正数既可以补上0,又可以防止其数值产生变化
            因此只要不是最后一个我们就或256
         */
        if (!isLast) {
            temp = temp | 256;
        }
        /*
            而对于最后一位,如果是正数,对于我们接收方,是完全未知接收到的(举例)2原编码是 010 还是10 还是 0010 还是00010
            故,在编码时我们记录一下就好了,zip方法中,只要是为最后一个bytes元素压缩,就记录0的个数,首次遇到非零我就退出,这样就能
            记录0的个数,接收方就可以通过属性count来避免译码时出现严重错误
         */
        if (isLast) {
            //最后一位我们不做或256补齐高位的操作,即使原来编码有零也不怕,因为编码时count已经帮我们记录过了前导0的个数
            //如果运气好是负数,那就更好直接就是八位,
            //如果是正数且有零,那就补零,补上count个前导零就OK了
            StringBuilder lastStrCode = new StringBuilder();
            for (int i = 0; i < count; i++) {
                lastStrCode.append("0");
            }
            lastStrCode.append(Integer.toBinaryString(temp));
            return lastStrCode.toString();
        }
        String str = Integer.toBinaryString(temp);
        str = str.substring(str.length() - 8);
        return str;
    }

 zip方法

只需注意该方法中最后一个byte的处理细节即可,其它代码和韩老师的讲解完全一致

/**
     * TODO ************字符串消息的压缩方法*************
     * 获取到的全部哈夫曼编码是字符串:类似于 "01101010110111010101..."
     * 这是二进制码,再将其八个一位转换成byte,存入byte[]中,全部存完以后就是我们要返回的byte数组
     * 如10101000转换成byte
     * 10101000(补码) 10101000-1=10100111(反码)  10100111取反11011000(原码)
     * 最前面的位是符号位,1表示负数
     * 2^3+2^4+2^6=88
     * 故最终转换成byte为-88
     *
     * @param bytes          字符串对应的原始字符数组
     * @param huffmanCodeMap 哈夫曼编码对照表(这是根据字符串消息生成的)
     * @return 压缩编码后的byte数组, 存放哈夫曼编码
     */
    public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodeMap) {
        StringBuilder code = new StringBuilder();
        //获得bytes对应的全部的字符串哈夫曼编码
        for (byte aByte : bytes) {
            code.append(huffmanCodeMap.get(aByte));
        }
//     用于测试   System.out.println(code.toString());
        //把字符串编码转换为byte[]
        //计算byte[]长度
        int len = (code.length() % 8 == 0) ? (code.length() / 8) : (code.length() / 8) + 1;
        byte[] huffmanCodeBytes = new byte[len];
        //为循环做准备工作
        String curCode;
        int index = 0;
        for (int i = 0; i < code.length(); i = i + 8) {
            //最后一位
            if ((i + 8) >= code.length()) {
                //下面改写了code是用于测试含前导零编码时是否能正确译码,
                // 并且,此处的进入条件要包括刚好够八位的情况,因此,此处要改为>=code.length
//                code= new StringBuilder("10101000101111111100100010111111110010001011111111001001010011" +
//                        "01110001110000011011101000111100101000101111111100110001001010011000011100");
                for (int j = i; j < code.length(); j++) {
                    if (code.substring(j,j+1).equals("0")) {
                        count++;
                    } else {
                        break;
                    }
                }
                curCode = code.substring(i);
            } else {//其他位
                curCode = code.substring(i, i + 8);
            }
            huffmanCodeBytes[index++] = (byte) Integer.parseInt(curCode, 2);
        }
        return huffmanCodeBytes;
    }

2.2对于非最后一个的byte元素

若不是最后一个byte我们都选择或上256,再转换成二进制,再截取后八位

实现

具体实现可以查看2.1的实现中的bytesToStringBitCode方法

3 总结

  1. 要注意增加静态属性 count 用于记录编码生成byte[ ]的最后一位时前导零的个数
  2. 测试时要避免多次压缩 比如 写完压缩方法韩老师会带我们测试 写完整体封装代码也会带我们测试,多次测试会导致count翻倍(因为是静态属性),后序译码会有问题,注意测试时不要多次编码
  3. 要注意编码时无论最后一个byte 用了8个还是 8个一下的二进制码,我们都要记录其前导零的个数!
  4. 如果想测试特殊情况(编码时最后一位byte用的码字是正数且含前导零),可以查看我提供的zip方法中的处理最后一位byte的注释,可以打开注释用于测试,注释内我们把编码修改了一下,让他符合特殊情况.打开后经本人测试,可以完成正确的译码.
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孤尘Java

感谢认可!感谢您的打赏!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值