1. 程式人生 > >20172303 2018-2019-1《程序設計與數據結構》哈夫曼樹編碼與解碼

20172303 2018-2019-1《程序設計與數據結構》哈夫曼樹編碼與解碼

exce eat temp 基礎 第一個 最小 charat 轉換 except

20172303 2018-2019-1《程序設計與數據結構》哈夫曼樹編碼與解碼

哈夫曼樹簡介

  • 定義:給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。
  • 帶權路徑長度(Weighted Path Length of Tree,簡記為WPL)
    • 結點的權:在一些應用中,賦予樹中結點的一個有某種意義的實數。
    • 結點的帶權路徑長度:結點到樹根之間的路徑長度與該結點上權的乘積。
    • 樹的帶權路徑長度(Weighted Path Length of Tree):定義為樹中所有葉結點的帶權路徑長度之和。
      技術分享圖片

哈夫曼樹代碼實現

  • 代碼參考了java創建哈夫曼樹和實現哈夫曼編碼

HuffmanNode類

  • 首先設置一個HuffmanNode類作為實現的基礎,每個結點都包含一個六項內容:權值、結點代表字母、字母的編碼、左孩子、右孩子和父結點,為了方便之後進行結點的比較,這裏還重新編寫了一下compareTo方法。
public int compareTo(HuffmanNode<T> o) {
    if (this.getWeight() > o.getWeight()){
        return -1;
    }
    else if (this.getWeight() < o.getWeight()){
        return 1;
    }
    return 0;
}

HuffmanTree類

  • HuffmanTree類裏有兩個方法,第一個方法createTree方法用於構造樹,第二個方法BFS方法是使用廣度優先遍歷來給每一個葉子結點進行編碼。具體方法及步驟在代碼中都已寫明。
public static HuffmanNode createTree(List<HuffmanNode<String>> nodes) {
    while (nodes.size() > 1){
        // 對數組進行排序
        Collections.sort(nodes);
        // 當列表中還有兩個以上結點時,構造樹
        // 獲取權值最小的兩個結點
        HuffmanNode left = nodes.get(nodes.size() - 2);
        left.setCode(0 + "");
        HuffmanNode right = nodes.get(nodes.size() - 1);
        right.setCode(1 + "");
        // 生成新的結點,新結點的權值為兩個子節點的權值之和
        HuffmanNode parent = new HuffmanNode(left.getWeight() + right.getWeight(), null);
        // 使新結點成為父結點
        parent.setLeft(left);
        parent.setRight(right);
        // 刪除權值最小的兩個結點
        nodes.remove(left);
        nodes.remove(right);
        nodes.add(parent);
    }
    return nodes.get(0);
}

public static List<HuffmanNode> BFS(HuffmanNode root){
    Queue<HuffmanNode> queue = new ArrayDeque<HuffmanNode>();
    List<HuffmanNode> list = new java.util.ArrayList<HuffmanNode>();

    if (root != null){
        // 將根元素加入隊列
        queue.offer(root);
        root.getLeft().setCode(root.getCode() + "0");
        root.getRight().setCode(root.getCode() + "1");
    }

    while (!queue.isEmpty()){
        // 將隊列的隊尾元素加入列表中
        list.add(queue.peek());
        HuffmanNode node = queue.poll();
        // 如果左子樹不為空,將它加入隊列並編碼
        if (node.getLeft() != null){
            queue.offer(node.getLeft());
            node.getLeft().setCode(node.getCode() + "0");
        }
        // 如果右子樹不為空,將它加入隊列並編碼
        if (node.getRight() != null){
            queue.offer(node.getRight());
            node.getRight().setCode(node.getCode() + "1");
        }
    }
    return list;
}

HuffmanMakeCode類

  • HuffmanMakeCode類用於將文件中的內容提取,放入數組並進行計數,這裏將數組長度設置為27,因為還對空格進行了計數,以便於解碼。具體方法及步驟在代碼中都已寫明。
public class HuffmanMakeCode {
    public static char[] word = new char[]{'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s'
            ,'t','u','v','w','x','y','z',' '};
    public static int[] number = new int[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

    public static String makecode(FileInputStream stream) throws IOException {
        //讀取文件(緩存字節流)
        BufferedInputStream in = new BufferedInputStream(stream);
        //一次性取多少字節
        byte[] bytes = new byte[2048];
        //接受讀取的內容(n就代表的相關數據,只不過是數字的形式)
        int n = -1;
        String a = null;
        //循環取出數據
        while ((n = in.read(bytes, 0, bytes.length)) != -1) {
            //轉換成字符串
            a = new String(bytes, 0, n, "GBK");
        }

        // 對文件內容進行計數
        count(a);

        return a;
    }

    // 實現對文件內容計數,內層循環依次比較字符串中的每個字符與對應字符是否相同,相同時計數;外層循環指定對應字符從a至空格
    public static void count(String str){
        for (int i = 0;i < 27;i++){
            int num = 0;
            for (int j = 0;j < str.length();j++){
                if (str.charAt(j) == word[i]){
                    num++;
                }
            }
            number[i] += num;
        }
    }

    public static char[] getWord() {
        return word;
    }

    public static int[] getNumber() {
        return number;
    }
}

HuffmanTest類

  • HuffmanTest類進行了文件的讀取,構造哈夫曼樹,編碼,解碼,文件的寫入五個步驟,其中前三個步驟使用之前三個類中的方法即可實現,這裏主要說一下後兩個步驟。
  • 解碼:解碼部分使用一個列表list4將編碼結果的字符串轉化到列表中去,然後定義了兩個變量,第一個變量用於每次依次獲取的編碼值,然後與list3(存儲編碼的列表)進行比較找到對應索引,然後將list2(存儲字母的列表)中對應索引值位置的字母加入第二個變量中,每次循環後刪除列表list4的第一個元素,循環直至list4為空時結束,第二個變量temp1中存儲的即為解碼結果。
  • 文件寫入:文件寫入就是很簡單的方法使用,這裏使用的是字符操作流(使用FileWriter類和FileReader類)的方法。
// 進行解碼
List<String> list4 = new ArrayList<>();
for (int i = 0;i < result.length();i++){
    list4.add(result.charAt(i) + "");
}
String temp = "";
String temp1 = "";
while (list4.size() > 0){
    temp += "" + list4.get(0);
    list4.remove(0);
    for (int i = 0;i < list3.size();i++){
        if (temp.equals(list3.get(i))){
            temp1 += "" + list2.get(i);
            temp = "";
        }
    }
}
System.out.println("文件解碼結果為: " + temp1);

// 寫入文件
File file = new File("C:\\Users\\45366\\IdeaProjects\\fwq20172303_Programming\\HuffmanTest2.txt");
Writer out = new FileWriter(file);
out.write(result);
out.close();

參考資料

  • 20172303 2017-2018-2 《程序設計與數據結構》第9周學習總結
  • 哈夫曼樹

20172303 2018-2019-1《程序設計與數據結構》哈夫曼樹編碼與解碼