ハフマン符号化は、バイナリファイルの圧縮と解凍を実現します

ハフマンツリーの基本的な紹介

1. n個の重みをn個のリーフノードとして指定し、バイナリツリーを構築します。ツリーの重み付きパス長(wpl)が最小に達した場合、そのようなバイナリツリーをハフマンツリー(ハフマンツリー)とも呼ばれる最適なバイナリツリーと呼びます。他の本はハフマンツリーとして翻訳されています。

2.ハフマンツリーは、重み付きパスの長さが最も短いツリーであり、重みが大きいノードはルートに近くなります。

ハフマンツリーのいくつかの重要な概念と例

1.パスとパスの長さ:ツリーでは、ノードから下に到達できる子ノードまたは孫ノード間のパスはパスと呼ばれます。パス内のブランチの数は、パスの長さと呼ばれます。ルートノードの層数を1と指定した場合、ルートノードからL番目の層のノードまでのパスの長さはL-1です。

2.ノードの重みと重み付きパスの長さ:ツリー内のノードに特定の意味を持つ値が割り当てられている場合、この値はノードの重みと呼ばれます。ノードの加重パス長は、ルートノードからノードまでのパスの長さとノードの重みの積です。

3.ツリーの加重パス長:ツリーの加重パス長は、すべてのリーフノードの加重パス長の合計として定義され、WPL(加重パス長)として示されます。加重が大きいほど、より遠くになります。ルートノードからのノード。最も近いバイナリツリーが最適なバイナリツリーです。

4.最小のWPLはハフマンツリーです

ハフマン数を生成する基本的な考え方

シーケンス{13、7、8、3、29、6、1}を与えて、ハフマンツリーに変換するように依頼します。

ハフマンツリーを形成する手順:

1.小さいものから大きいものへと並べ替えます。各データはノードであり、各ノードは最も単純な二分木と見なすことができます。

2.ルートノードの重みが最小の2つの二分木を取り出します

3.新しい二分木を形成するために、新しい二分木のルートノードの重みは、前の2つの二分木のルートノードの重みの合計です。  

4.ルートノードの重みに従ってこの新しいバイナリツリーを再度並べ替え、シーケンス内のすべてのデータが処理されてハフマンが取得されるまで、1-2-3-4の手順を繰り返します。

ダイアグラム

{13、7、8、3、29、6、1}  

1. {1、3、6、7、8、13、29}を並べ替えます

2.新しいツリーを並べ替えて再構築します

等々...

コード

ビルドノード

class Node implements Comparable<Node>{
    public int value;
    public Node left;
    public Node right;

    public Node(int value){
        this.value = value;
    }

    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;
    }
}

ハフマンツリーを作成する

public static Node createHuffmanTree(int[]arr){
        ArrayList<Node> nodes = new ArrayList<>();
        for (int i = 0; i < arr.length; i++) {
            nodes.add(new Node(arr[i]));
        }
        while(nodes.size() > 1){
            Collections.sort(nodes);
            Node left = nodes.get(0);
            Node right = nodes.get(1);
            Node parent = new Node(left.value + right.value);
            parent.left = left;
            parent.right = right;
            nodes.add(parent);
            nodes.remove(left);
            nodes.remove(right);
        }
        return nodes.get(0);
    }

ハフマン符号化

1.ハフマンコーディングは、ハフマンコーディングとも呼ばれるハフマンコーディングとも呼ばれます。これは、コーディング方法であり、プログラムアルゴリズムに属します。

2.ハフマン符号化は、電気通信におけるハフマンツリーの古典的なアプリケーションの1つです。

3.ハフマン符号化は、データファイルの圧縮に広く使用されています。その圧縮率は通常20%から90%の間です

4.ハフマンコードは、可変ワード長コーディング(VLC)の一種です。ハフマンは1952年に最高のコーディングと呼ばれるコーディング方法を提案しました

通信分野での情報処理方法1-固定長コーディング私はJavaが好きですか?Javaは好きですか//合計40文字(スペースを含む)  

105 32108105107101 32108105107101 32108105107101 32106 97118 97 32100111 32121111117 32108105107101 32 97 32106 97118 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 01101001 01101111 01010101 00100000 01100100 01101111 00100000 01101001 01101111 01010101 00100000 01100100 01101111 00100000 01101001 01101111 01010101 00100000 0110000 0110000

情報はバイナリで送信され、全長は359(スペースを含む)です。

オンライントランスコーディングツール

通信分野における情報処理2-可変長符号化

私はJavaが好きですか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 java do you like a java」データを送信すると、エンコーディングは10010110100です。  

文字エンコードを他の文字エンコードのプレフィックスにすることはできません。この要件を満たすエンコードはプレフィックスエンコードと呼ばれます。つまり、繰り返されるエンコードと一致することはできません。

通信分野における情報処理3-ハフマン符号化

私はJavaが好きですか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 java do you like a java」文字列は、符号化に対応します(ここで使用するロスレス圧縮に注意してください)1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110

長さ:133

説明:

1.元の長さは359で、圧縮されています(359-133)/ 359 = 62.9%

2.このコードはプレフィックスコードを満たします。つまり、文字コードを他の文字コードのプレフィックスにすることはできません。マッチングに曖昧さを生じさせません

このハフマンツリーはソート方法によって異なる場合があるため、対応するハフマンコードは完全に同じではありませんが、wplは同じで最小です。たとえば、各世代を作成する場合、新しい二分木は常に同じ重みを持つ最後の二分木にランク付けされます。結果の二分木は次のとおりです。

ハフマンコーディングコードの実装

ビルドノード

class Node implements Comparable<Node>{
    public Byte data;
    public int weight;
    public Node left;
    public Node right;

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

    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{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }

    @Override
    public int compareTo(Node o) {
        return this.weight - o.weight;
    }
}

1.各データをノードに変換し、リストコレクションに保存します

private static ArrayList<Node> getList(byte[]bytes){
        ArrayList<Node> 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,map.get(b) + 1);
        }
        //遍历map
        for(Map.Entry<Byte,Integer> entry : map.entrySet()){
            nodes.add(new Node(entry.getKey(),entry.getValue()));
        }
        return nodes;
    }

2.リストコレクションに従ってハフマンツリーを取得します

private static Node createHuffmanTree(List<Node> nodes){
        while(nodes.size() > 1){
            Collections.sort(nodes);
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);
            Node parent = new Node(null,leftNode.weight + rightNode.weight);
            parent.left = leftNode;
            parent.right = rightNode;
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            nodes.add(parent);
        }
        return nodes.get(0);
    }

3.ハフマンツリーに従ってハフマンコーディングテーブルを取得します(ここでは、パラメータ転送を容易にするためにメソッドオーバーロードメカニズムが使用されます)

static Map<Byte,String> huffmanCodes = new HashMap<>();
    static StringBuilder stringBuilder = new StringBuilder();

    private static Map<Byte,String> getCodes(Node root){
        if (root == null)
            return null;
        getCodes(root,"",stringBuilder);
        return huffmanCodes;
    }

    //根据哈夫曼树获得哈夫曼编码
    private static void getCodes(Node node,String code,StringBuilder stringBuilder){
        StringBuilder stringBuilder1 = new StringBuilder(stringBuilder);
        stringBuilder1.append(code);
        if(node.data == null) {//非叶子节点
            //向左递归
            getCodes(node.left,"0",stringBuilder1);
            //向右递归
            getCodes(node.right,"1",stringBuilder1);
        }else {
            huffmanCodes.put(node.data,stringBuilder1.toString());
        }
    }

4.元のバイト配列とハフマンコーディングテーブルに従ってデータを圧縮します

private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
        StringBuilder builder = new StringBuilder();
        for(byte b : bytes){
            builder.append(huffmanCodes.get(b));
        }
        int length = (builder.length() + 7) / 8 ;
        byte[] by = new byte[length];
        int index = 0;
        String str;
        for (int i = 0; i < builder.length(); i += 8) {
            if((i + 8) > builder.length()){
                str = builder.substring(i);
                by[index] = (byte)Integer.parseInt(str,2);
                index++;
            }else {
                str = builder.substring(i, i + 8);
                by[index] = (byte)Integer.parseInt(str,2);
                index++;
            }
        }
        return by;
    }

5.後で使用しやすくするために、圧縮をカプセル化します

private static byte[] huffmanZip(byte[] bytes){
        //获取ArrayList<Node>集合
        ArrayList<Node> nodes = getList(bytes);
        //获取哈夫曼树
        Node root = createHuffmanTree(nodes);
        //获取哈夫曼编码表
        Map<Byte,String>huffmanCodes = getCodes(root);
        //压缩数据
        byte[]huffmanCodesBytes = zip(bytes,huffmanCodes);
        return huffmanCodesBytes;
    }

圧縮されたハフマンバイト配列は[-88、-65、-56、-65、-56、-65、-55、77、-57、6、-24、-14、-117、-4、-60、 -90、28]

デコード(解凍)

6.バイトをバイナリに変換するメソッドを記述します。正の数は上位8ビットを埋める必要があり、負の数は8番目の補数をインターセプトする必要があります。これが最後の場合は、操作や変換を行う必要はありません。直接

/**
     * 将字节类型的十进制转换为二进制的字符串类型
     * @param flag 是否是最后一位,最后一位不需要补高位
     * @param b 带转换的字节
     * @return
     */
    private 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;
    }

7.解凍。ここでは、最初にハフマン符号化バイト配列をStringBuilderに変換し、次にハフマン符号テーブルを反転して復号化テーブルを取得し、それを復号化テーブルに対してデコードします。そして、リストコレクションにカプセル化されます

/**
     * 解压
     * @param huffmanCodesBytes 哈夫曼编码后的字节数组
     * @param huffmanCodes 哈夫曼编码表
     * @return 哈夫曼编码前的字节数组
     */
    public static byte[] deCode(byte[]huffmanCodesBytes,Map<Byte,String> huffmanCodes){
        boolean flag = true;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < huffmanCodesBytes.length; i++) {
            byte b = huffmanCodesBytes[i];
            if(i == huffmanCodesBytes.length - 1)
                flag = false;
            builder.append(byteToBitString(flag,b));
        }
        String str = builder.toString();
        //将hashMap反转
        Map<String,Byte> map = new HashMap<>();
        for (Map.Entry<Byte,String> entry : huffmanCodes.entrySet()){
            map.put(entry.getValue(),entry.getKey());
        }
        ArrayList<Byte> list = new ArrayList<>();
        int start = 0;
        int end = 1;
        while(start < str.length()){
            while(end < str.length() && map.get(str.substring(start,end)) == null){
                end++;
            }
            list.add(map.get(str.substring(start,end)));
            start = end;
        }
        byte[] b = new byte[list.size()];
        for (int i = 0; i < b.length; i++) {
            if(list != null) {
                b[i] = list.get(i);
            }
        }
        return b;
    }

テスト

public static void main(String[] args) {
        String constant = "i like like like java do you like a java";
        System.out.println(constant);
        byte[] constantBytes = constant.getBytes();
        byte[] huffmanCodesBytes = huffmanZip(constantBytes);
        Map<Byte, String> huffmanCodes = getCodes(createHuffmanTree(getList(constantBytes)));
        byte[] sourceBytes = deCode(huffmanCodesBytes, huffmanCodes);
        System.out.println(new String(sourceBytes));
    }

エンコードの前後でエンコードがまったく同じであることがわかります。これにより、圧縮と解凍のプロセス全体が実現されます。

練習

バイナリファイルの圧縮と解凍を実現します

圧縮

public static void zipFile(String srcFile,String dstFile){
        //创建输入流
        FileInputStream is = null;
        //创建输出流和对象输出流
        FileOutputStream os = null;
        ObjectOutputStream oos = null;
        try {
            is = new FileInputStream(srcFile);
            byte[] b = new byte[is.available()];
            is.read(b);
            os = new FileOutputStream(dstFile);
            oos = new ObjectOutputStream(os);
            byte[] huffmanBytes = huffmanZip(b);
            oos.writeObject(huffmanBytes);
            oos.writeObject(huffmanCodes);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

解凍する

public static void unZipFile(String zipFile,String dstFile){
        //创建输入流和对象输入流
        FileInputStream is = null;
        ObjectInputStream ois = null;
        //创建输出流
        FileOutputStream os = null;
        try {
            is = new FileInputStream(zipFile);
            ois = new ObjectInputStream(is);
            byte[] huffmanBytes = (byte[])ois.readObject();
            Map<Byte,String> huffmanCodes = (Map<Byte,String>)ois.readObject();
            byte[] bytes = deCode(huffmanBytes,huffmanCodes);
            os = new FileOutputStream(dstFile);
            os.write(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

テスト

        //测试压缩文件
        /*String srcFile = "d://src.bmp";
        String dstFile = "d://src.zip";
        zipFile(srcFile,dstFile);
        System.out.println("压缩成功!");*/

        //测试解压文件
        String zipFile = "d://src.zip";
        String dstFile = "d://src1.bmp";
        unZipFile(zipFile,dstFile);
        System.out.println("解压成功!");

圧縮前後の画像比較

おすすめ

転載: blog.csdn.net/qq_45796208/article/details/111631837