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
類測試結果
- 編碼寫入文件
參考資料