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

20172303 2018-2019-1《程式設計與資料結構》哈夫曼樹編碼與解碼

20172303 2018-2019-1《程式設計與資料結構》哈夫曼樹編碼與解碼

哈夫曼樹簡介

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

哈夫曼樹程式碼實現

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();

參考資料