Java數據壓縮算法——哈夫曼樹

——在這個特殊的日子裏,向烈士們致敬!!!

目錄

一、簡介

二、實現思路

        2.1 路徑

        2.2 節點的權及帶權路徑長度

        2.3 樹的帶權路徑長度

        2.4 霍夫曼樹的定義

        2.5 構造霍夫曼樹

三、代碼實現


一、簡介

          給定N個權值作爲N個葉子節點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹爲最優二叉樹,也稱爲哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。

        在計算機數據處理中,哈夫曼編碼使用變長編碼表對源符號(如文件中的一個字母)進行編碼,其中變長編碼表是通過一種評估來源符號出現機率的方法得到的,出現機率高的字母使用較短的編碼,反之出現機率低的則使用較長的編碼,這便使編碼之後的字符串的平均長度、期望值降低,從而達到無損壓縮數據的目的。

      哈夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度,就是樹中所有的葉結點的權值乘上其到根結點的路徑長度(若根結點爲0層,葉結點到根結點的路徑長度爲葉結點的層數)。樹的路徑長度是從樹根到每一結點的路徑長度之和,記爲WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),N個權值Wi(i=1,2,...n)構成一棵有N個葉結點的二叉樹,相應的葉結點的路徑長度爲Li(i=1,2,...n)。可以證明哈夫曼樹的WPL是最小的。

                                                                                                                                                                      ——百度百科

二、實現思路

        2.1 路徑

                                          

        2.2 節點的權及帶權路徑長度

         

        2.3 樹的帶權路徑長度

                               

        2.4 霍夫曼樹的定義

                   每次挑選值最小的樹節點,將之合成爲一個節點,值爲兩個之和,取代原來兩個,直到只有一個樹節點——也就是根。 

      對比下圖哪個爲霍夫曼樹:

     

                    圖1的WPL爲:7*1+5*2+4*3+1*3 = 32

                    圖2的WPL爲:7*2+5*2+4*2+1*2 = 34

                     故:圖1爲霍夫曼樹

        2.5 構造霍夫曼樹

               現有以下編碼:AABCCADDEFFFCAABBCGAGGCCYYY

                統計每個編碼出現的次數

編碼 出現次數
A 6
B 3
C 6
D 2
E 1
F 3
G 3
3

                     將編碼根據出現次數進行排序,並且從排序後的數組中取出兩個最小的數進行將之合爲一個節點,取代原來兩個,直到只有一個節點

E D B F G Y A C
1 2 3 3 3 3 6 6

                                                               

                 

(E,D) B F G Y A C
3 3 3 3 3 6 6

                                                       

                                             

F G Y A C (E,D,B)
3 3 3 6 6 6

                                                                 

                                                                                                                          注:這是新的霍夫曼樹

Y A C (E,D,B) (F,G)
3 6 6 6 6

                                                                        注:這是新的霍夫曼樹

                    

C (E,D,B) (F,G) (Y,A)
6 6 6 9

                                                        

(F,G) (Y,A) (E,D,B,C)
6 9 12

                                                        

(E,D,B,C) (F,G,Y,A)
12 15

    

(E,D,B,C,F,G,Y,A)
27

 編碼規則:從根節點出發,向左標記爲0,向右標記爲1。

 根據編碼規則,進行編碼

節點 編碼
A 111
B 010
C 00
D 0111
E 0110
F 100
G 101
Y 1110

故AABCCADDEFFFCAABBCGAGGCCYYY對應編碼爲:

111 111 010 00 00 111 0111 0111 0110 100 100 100 00 111 111 010 010 00 101 101 00 00 1110 1110 1110

數組的排序可能和代碼排序不一致,故編碼可能和代碼結果不同

三、代碼實現

@Data
public class HuffmanNode implements Comparable<HuffmanNode>{

    private char letter;
    private int count;//字符letter出現的頻率,即權重
    private HuffmanNode left;
    private HuffmanNode right;

    public HuffmanNode(char letter, int count) {
        this.letter = letter;
        this.left = null;
        this.right = null;
        this.count = count;
    }

    public HuffmanNode(HuffmanNode left, HuffmanNode right) {
        this.letter = '?';
        this.left = left;
        this.right = right;
        this.count = left.count + right.count;
    }

    //爲了進行排序
    @Override
    public int compareTo(HuffmanNode o) {
        return count - o.count;
    }



}
package com.zcxy.tree.HT;

import com.zcxy.bean.HuffmanNode;

import java.util.*;

/**
 * @Author: Eating melons the masses
 * @Date: 2020/3/22 16:06
 */
public class HuffmanTree {

    public static Map<Character,String> map = new HashMap<>();

    public static void buildHuffman(String code){
        if (code != null){
            // 將編碼拆分並統計
            char[] chars = code.toCharArray();
            Map<Character,List<Character>> codeNumber = codeNumber(chars);
            codeNumber.forEach((k,v) -> {
                System.out.println(k+" : "+v.size());
            });
            // 根據編碼統計的結果構建節點
//            HuffmanNode huffmanNode = generateTree(codeNumber);
            List<HuffmanNode> list = new ArrayList<>();
            codeNumber.forEach((k,v) -> {
                list.add(new HuffmanNode(k,v.size()));
            });
            HuffmanNode huffmanNode1 = generateTree(list);
            // 根據霍夫曼樹的性質-【左0,右1】進行編碼
            code = "";
            generateCodes(huffmanNode1, code);

            StringBuilder stringBuilder = buildCode(map, chars);
            System.out.println("編碼:"+stringBuilder.toString());
            StringBuilder deCode = deCode(stringBuilder, map);
            System.out.println("解碼:"+deCode.toString());
        }
    }

    // 解碼
    public static StringBuilder deCode(StringBuilder sb,Map<Character,String> map){
        StringBuilder sbr = new StringBuilder();
        String[] split = sb.toString().split(" ");
        for (String code : split) {
            map.forEach((k,v) -> {
                if (code.equals(v)){
                    sbr.append(k);
                }
            });
        }
        return sbr;
    }

    // 編碼
    public static StringBuilder buildCode(Map<Character,String> map,char[] chars){
        StringBuilder sb = new StringBuilder();
        if (!map.isEmpty()){
            for (char aChar : chars) {
                map.forEach((k,v) -> {
                    if (aChar == k){
                        sb.append(v).append(" ");
                    }
                });
            }
        }
        return sb;
    }

    //生成編碼,存放在HashMap中,key對應letter,value對應letter的二進制編碼
    public static void generateCodes(HuffmanNode root, String code) {
        if (root.getLeft() == null && root.getRight() == null) {
            map.put(root.getLetter(), code);
        } else {
            generateCodes(root.getLeft(), code + "0");
            generateCodes(root.getRight(), code + "1");
        }
    }




    //構造霍夫曼樹(非遞歸)
    private static HuffmanNode generateTree(Map<Character,List<Character>> codeNumber) {
        List<HuffmanNode> list = new ArrayList<>();
        codeNumber.forEach((k,v) -> {
            list.add(new HuffmanNode(k,v.size()));
        });
        // 將當前節點根據權值進行排序 並構建最小霍夫曼樹
        while (true){
            Collections.sort(list);
            HuffmanNode a = list.remove(0);
            if (list.isEmpty()){
                return a;
            }
            HuffmanNode b = list.remove(0);
            list.add(new HuffmanNode(a,b));
        }
    }

    // 將當前節點根據權值進行排序 並構建最小霍夫曼樹 (遞歸)
    private static HuffmanNode generateTree(List<HuffmanNode> list) {
        Collections.sort(list);
        HuffmanNode a = list.remove(0);
        if (!list.isEmpty()){
            HuffmanNode b = list.remove(0);
            list.add(new HuffmanNode(a,b));
            return generateTree(list);
        }
        return a;
    }



    private static Map<Character,List<Character>> codeNumber(char[] chars) {
        if (chars.length > 0){
            Map<Character,List<Character>> codeNumber = new HashMap<>();
            Set<Character> sets = new HashSet<>();
            for (char aChar : chars) {
                sets.add(aChar);
            }
            sets.forEach(s -> {
                List<Character> codeList = new ArrayList<>();
                for (char aChar : chars) {
                    if (s.equals(aChar)){
                        codeList.add(s);
                    }
                }
                codeNumber.put(s,codeList);
            });
            return codeNumber;
        }
        return null;
    }

    public static void main(String[] args) {
        buildHuffman("AABCCADDEFFFCAABBCGAGGCCYYY");
    }

}
                {\__/}                                          {\__/}
                ( ·-·)                                          (·-· )
                / >------------------------------------------------< \
                         |      ☆                            |
                         |         ☆                         |
                         |  ★                                |
                         |         ☆                         |
                         |      ☆                            |
                         |                                   |
                         -------------------------------------

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章