zoukankan      html  css  js  c++  java
  • 赫夫曼编码

    赫夫曼编码与解码

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

    赫夫曼编码原理剖析

    赫夫曼树根据排序方法不同,可能不太一样,对应的赫夫曼编码也不完全一样,但长度总是相同的


    用赫夫曼编码实现文件压缩和解压

    赫夫曼编码压缩文件注意事项

    如果文件本身就是经过压缩处理的,那么使用赫夫曼编码再压缩效率不会有明显变化, 比如视频,ppt等等文件
    ➢赫夫曼编码是按字节来处理的,因此可以处理所有的文件(二进制文件、文本文件)
    如果一个文件中的内容,重复的数据不多,压缩效果也不会很明显

    package com.xudong.DataStructures;
    
    import java.io.*;
    import java.util.*;
    
    public class HuffmanCodeDemo {
        public static void main(String[] args) {
            String content = "i like like like java do you like a java";
            byte[] contentBytes = content.getBytes();
            System.out.println("原字符串长度:" + contentBytes.length);
            byte[] huffmanCodesBytes = haffmanZip(contentBytes);
            System.out.println("压缩后的结果是:" + Arrays.toString(huffmanCodesBytes) + "  长度=" + huffmanCodesBytes.length);
    
            byte[] sourceBytes = decode(huffmanCodes, huffmanCodesBytes);
            System.out.println("原来的字符串=" + new String(sourceBytes));
    
            System.out.println("------------------------------");
            //测试压缩文件
            String srcFile = "C:\Users\Shinelon\Desktop\java大数据开发大纲.jpg";
            String destFile = "C:\Users\Shinelon\Desktop\java大数据开发大纲.zip";
            zipFile(srcFile,destFile);
            System.out.println("压缩文件成功!");
    
            //测试解压文件
            String zipFile = "C:\Users\Shinelon\Desktop\java大数据开发大纲.zip";
            String unZipFile = "C:\Users\Shinelon\Desktop\java大数据开发大纲2.jpg";
            unZipFile(zipFile,unZipFile);
            System.out.println("解压文件成功!");
    
    
            /*
            List<Node1> nodes = getNodes(contentBytes);
            System.out.println("nodes=" + nodes);
    
            System.out.println("赫夫曼树,前序遍历:");
            Node1 huffmanTreeRoot = createHuffmanTree(nodes);
            huffmanTreeRoot.preOrder();
    
            Map<Byte, String> huffmanCodes = getCodes(huffmanTreeRoot);
            System.out.println("生成的哈夫曼编码表:" + huffmanCodes);
    
            byte[] huffmanCodeBytes = zip(contentBytes, huffmanCodes);
            System.out.println("huffmanCodeBytes=" + Arrays.toString(huffmanCodeBytes));
    
             */
        }
    
        //对文件解压
        public static void unZipFile(String zipFile,String destFile){
            ObjectInputStream ois = null;
            FileOutputStream fos = null;
            try {
                //创建文件输入流
                FileInputStream fis = new FileInputStream(zipFile);
                //创建对象输入流
                ois = new ObjectInputStream(fis);
                //读取byte数组 huffmanBytes
                byte[] huffmanBytes = (byte[]) ois.readObject();
                //读取赫夫曼编码表
                Map<Byte,String> huffmanCodes = (Map<Byte, String>) ois.readObject();
                //解码
                byte[] bytes = decode(huffmanCodes, huffmanBytes);
                //将bytes数组写入到目标文件
                fos = new FileOutputStream(destFile);
                //写入数据到destFile
                fos.write(bytes);
    
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        //对文件进行压缩
        /**
         * @param srcFile 原文件路径
         * @param destFile 压缩后存放的目录
         */
        public static void zipFile(String srcFile,String destFile){
            FileInputStream fis = null;
            ObjectOutputStream oos = null;
            try {
                //创建文件输入流
                fis = new FileInputStream(srcFile);
                //创建一个和源文件一样大小的byte[]
                byte[] b = new byte[fis.available()];
                //读取文件
                fis.read(b);
                //对源文件进行压缩
                byte[] huffmanBytes = haffmanZip(b);
                //创建文件输出流,存放压缩文件
                FileOutputStream fos = new FileOutputStream(destFile);
                //创建一个和文件输出流关联的ObjectOutputStream
                oos = new ObjectOutputStream(fos);
                //把赫夫曼编码后的字节数组写入压缩文件
                oos.writeObject(huffmanBytes);
                //将赫夫曼编码表写入压缩文件
                oos.writeObject(huffmanCodes);
    
            } catch (IOException e) {
                System.out.println(e.getMessage());
            } finally {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
        }
    
        //数据解码(解压)
        //1.将huffmanCodesBytes[-88, -65, -56, -65...]转成赫夫曼编码对应的二进制字符串"101010001011..."
        //2.将对应的二进制字符串对照赫夫曼编码表转换成原来的字符串"i like like like..."
    
        /**
         * @param huffmanCodes 赫夫曼编码表
         * @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));
            }
    
            //将赫夫曼编码表的键值调换,进行解码
            Map<String, Byte> map = new HashMap<>();
            for (Map.Entry<Byte,String> entry : huffmanCodes.entrySet()){
                map.put(entry.getValue(),entry.getKey());
            }
    
            //存放byte
            List<Byte> list = new ArrayList<>();
            //扫描stringBuilder
            for (int i = 0; i < stringBuilder.length();) {
                int count = 1;
                boolean flag = true;
                Byte b = null;
    
                while (flag){
                    //递增的取出key
                    String key = stringBuilder.substring(i, i + count);
                    b = map.get(key);
                    if (b == null){//没有匹配到
                        count++;
                    }else {//匹配到
                        flag = false;
                    }
                }
                list.add(b);
                i += count;//i 移动到count
            }
            //此时list存放了解压后的所有字符
            byte b[] = new byte[list.size()];
            for (int i = 0; i < b.length; i++) {
                b[i] = list.get(i);
            }
            return b;
        }
    
    
    
        /**
         * @param flag 标志是否需要补高位,如果是最后一个字节,则无需补高位
         * @param b 传入的byte
         * @return 是该 b 对应补码返回的二进制字符串
         */
        private static String byteToBitString(boolean flag,byte b){
           //使用变量保存b
           int temp = b;//将b转成int
           //如果是正数,则需要补高位
           if (flag){
               temp |= 256;//按或与
           }
            String str = Integer.toBinaryString(temp);//返回的是temp对应的二进制补码
            if (flag){
                return str.substring(str.length() - 8);
            }else {
                return str;
            }
        }
    
    
        /**
         * @param bytes 原始字符串对应的字节数组
         * @return 经过赫夫曼编码处理后的字节数组
         */
        private static byte[] haffmanZip(byte[] bytes){
            List<Node1> nodes = getNodes(bytes);
            //根据nodes创建的赫夫曼树
            Node1 huffmanTreeRoot = createHuffmanTree(nodes);
            //对应的赫夫曼编码(根据赫夫曼树)
            Map<Byte, String> huffmanCodes = getCodes(huffmanTreeRoot);
            //根据生成的赫夫曼编码,压缩得到压缩后的赫夫曼编码字节数组
            byte[] huffmanCodeBytes = zip(bytes, huffmanCodes);
            return huffmanCodeBytes;
        }
    
    
    
        /**注:是补码的形式进行编码
         * @param bytes 原始的字符串对应的byte[]
         * @param huffmanCodes 生成的赫夫曼编码map
         * @return 拼接后的编码,8位一个存储在byte中
         */
        //将字符串对应的byte[]数组,通过生成的赫夫曼编码表,返回一个赫夫曼编码压缩后的byte[]
        private static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCodes){
            //利用huffmanCodes将bytes转成赫夫曼编码对应的字符串
            StringBuilder stringBuilder = new StringBuilder();
            //遍历传入的字节数组
            for (byte b : bytes){
                stringBuilder.append(huffmanCodes.get(b));
            }
            //统计返回byte[] huffmanCodeBytes 长度
            int len;
            if (stringBuilder.length() % 8 == 0){
                len = stringBuilder.length() / 8;
            }else {
                len = stringBuilder.length() / 8 + 1;
            }
    
            //创建存储压缩后的byte数组
            byte[] huffmanCodeBytes = new byte[len];
            int index = 0;//记录是第几个byte
            for (int i = 0; i < stringBuilder.length(); i += 8) {
                String strByte;
                if (i + 8 > stringBuilder.length()){//不够8位
                    strByte = stringBuilder.substring(i);
                }else {
                    strByte = stringBuilder.substring(i,i + 8);
                }
                //将strByte 转成 一个 byte ,放入huffmanCodeBytes中
                huffmanCodeBytes[index] = (byte) Integer.parseInt(strByte,2);
                index++;
            }
            return huffmanCodeBytes;
        }
    
        //重载getCodes
        private static Map<Byte,String> getCodes(Node1 root){
            if (root == null){
                return null;
            }
            //处理root的左子树
            getCodes(root.left,"0",stringBuilder);
            //处理root的右子树
            getCodes(root.right,"1",stringBuilder);
            return huffmanCodes;
        }
    
    
        //将赫夫曼编码表存放到Map中,形如 32 -> 01 , 97 -> 100
        static Map<Byte,String> huffmanCodes = new HashMap<>();
        //生成的赫夫曼编码需要拼接路径,定义StringBuilder存储某个叶子节点的路径
        static StringBuilder stringBuilder = new StringBuilder();
    
        /**
         * 功能:将传入的node节点的所有叶子节点的赫夫曼编码得到,并放入huffmanCodes集合
         * @param node 传入节点
         * @param code 路径:左子节点是 0 ,右子节点 1
         * @param stringBuilder 用于拼接路径
         */
        private static void getCodes(Node1 node,String code,StringBuilder stringBuilder){
            StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
            //将code加入到StringBuilder2
            stringBuilder2.append(code);
            if (node != null){//如果node == null不处理
                //判断当前node是叶子节点还是非叶子节点
                if (node.data == null){//非叶子节点
                    //向左递归
                    getCodes(node.left,"0",stringBuilder2);
                    //向右递归
                    getCodes(node.right,"1",stringBuilder2);
                }else {//说明是叶子节点
                    //存储
                    huffmanCodes.put(node.data,stringBuilder2.toString());
                }
            }
        }
    
    
        //前序遍历的方法
        public static void preOrder(Node1 root){
            if (root != null){
                root.preOrder();
            }else {
                System.out.println("赫夫曼树为空!");
            }
        }
    
        /**
         * @param bytes 接收字节数组
         * @return 返回List形式
         */
        private static List<Node1> getNodes(byte[] bytes){
            ArrayList<Node1> nodes = new ArrayList<>();
    
            //统计每一个byte出现的次数
            HashMap<Byte, Integer> counts = new HashMap<>();
            for (byte b : bytes){
                Integer count = counts.get(b);
                if (count == null){//Map还没有这个数据时
                    counts.put(b,1);
                }else {
                    counts.put(b,count + 1);
                }
            }
            //遍历map,把每一个键值对转换成一个node对象,加入到nodes集合
            for (Map.Entry<Byte,Integer> entry : counts.entrySet()){
                nodes.add(new Node1(entry.getKey(),entry.getValue()));
            }
            return nodes;
        }
    
        //通过List创建对应的赫夫曼树
        public static Node1 createHuffmanTree(List<Node1> nodes){
    
            while (nodes.size() > 1){
                //排序
                Collections.sort(nodes);
    
                //1.取出权值最小的节点(二叉树)
                Node1 leftNode = nodes.get(0);
                //2.取出权值第二小的节点(二叉树)
                Node1 rightNode = nodes.get(1);
                //3.创建一个新的二叉树,跟节点root只取权值
                Node1 parent = new Node1(null,leftNode.weight + rightNode.weight);
                parent.left = leftNode;
                parent.right = rightNode;
                //4.从ArrayList删除处理过的二叉树
                nodes.remove(leftNode);
                nodes.remove(rightNode);
                //5.将parent加入到nodes
                nodes.add(parent);
            }
            //最后的节点就是赫夫曼树root节点
            return nodes.get(0);
        }
    }
    
    //创建Node,待处理数据和权值
    class Node1 implements Comparable<Node1>{
        Byte data;//存放数据本身,即ASCII码
        int weight;//权值,即字符出现的个数
        Node1 left;
        Node1 right;
    
        public Node1(Byte data, int weight) {
            this.data = data;
            this.weight = weight;
        }
    
        @Override
        public String toString() {
            return "Node1{" +
                    "data=" + data +
                    ", weight=" + weight +
                    '}';
        }
    
        @Override
        public int compareTo(Node1 o) {
            return this.weight - o.weight;
        }
    
        //前序遍历
        public void preOrder(){
            System.out.println(this);
            if (this.left != null){
                this.left.preOrder();
            }
            if (this.right != null){
                this.right.preOrder();
            }
        }
    }
    
  • 相关阅读:
    FZU 2112 并查集、欧拉通路
    HDU 5686 斐波那契数列、Java求大数
    Codeforces 675C Money Transfers 思维题
    HDU 5687 字典树插入查找删除
    HDU 1532 最大流模板题
    HDU 5384 字典树、AC自动机
    山科第三届校赛总结
    HDU 2222 AC自动机模板题
    HDU 3911 线段树区间合并、异或取反操作
    CodeForces 615B Longtail Hedgehog
  • 原文地址:https://www.cnblogs.com/nnadd/p/13440719.html
Copyright © 2011-2022 走看看