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

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

哈夫曼樹簡介

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

哈夫曼樹的構造

  • 哈夫曼樹並不惟一,但帶權路徑長度必定是相同的。下面用一個例子來解釋哈夫曼樹的構造。
  • (1)假設有8個結點,其權值大小以下
  • (2)從中選擇最小的兩個結點2和3,合成一顆樹,根結點的值爲兩個孩子結點的值相加
  • (3)從剩餘的結點(包括新生成的樹的根結點)中在選擇兩個最小的結點5和6,構造新樹
  • (4)繼續從中進行選擇,此時選擇7和10,由於這兩個結點都和以前生成的樹無關,因此他們從新生成一棵樹(若是兩個數的和正好是下一步的兩個最小數的其中的一個,那麼這個樹直接往上生長就能夠了,若是這兩個數的和比較大,不是下一步的兩個最小數的其中一個,那麼就並列生長)
  • (5)從19,21,32,11,17中進行選擇,選擇11和17,構造新樹
  • (6)選擇19和21,構造新樹
  • (7)選擇28和32,構造新樹
  • (8)此時只剩下兩顆樹,將其從新構造一顆新樹,一顆哈夫曼樹就成型了

哈夫曼樹代碼實現

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中存儲的即爲解碼結果。
// 進行解碼
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);
  • 文件寫入:文件寫入就是很簡單的方法使用,這裏使用的是字符操做流(使用FileWriter類和FileReader類)的方法。
// 寫入文件
File file = new File("C:\\Users\\45366\\IdeaProjects\\fwq20172303_Programming\\HuffmanTest2.txt");
Writer out = new FileWriter(file);
out.write(result);
out.close();

實驗結果

  • 所讀取的文件
  • HuffmanTest類測試結果

  • 編碼寫入文件

參考資料

相關文章
相關標籤/搜索