數據結構之哈夫曼樹
實驗要求:
- 設有字符集:S={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}。
給定一個包含26個英文字母的文件,統計每個字符出現的概率,根據計算的概率構造一顆哈夫曼樹。
並完成對英文文件的編碼和解碼。 - 要求:
- 准備一個包含26個英文字母的英文文件(可以不包含標點符號等),統計各個字符的概率
- 構造哈夫曼樹
- 對英文文件進行編碼,輸出一個編碼后的文件
- 對編碼文件進行解碼,輸出一個解碼后的文件
- 撰寫博客記錄實驗的設計和實現過程,並將源代碼傳到碼雲
- 把實驗結果截圖上傳到雲班課
什么是哈夫曼樹?
- 基本概念
- 最優二叉樹:平均編碼長度最短。
- 結點之間的路徑:一個結點到另一個結點所經過的結點次序。
- 結點之間的路徑長度:兩個結點之間邊的條數。
- 樹的路徑長度:從根結點到每個葉子結點的路徑長度之和。
- 帶權路徑: 路徑上加上的實際意義 。如汽車到下一站的距離我們叫做權值.
- 樹的帶權路經長度:每個葉子結點到根的路徑長度權值之和,記作WPL。
還是汽車的例子,汽車到達天津有2條路 可以走。第一條路經過3個站,每個站相距13km。第二條有2個站,每個站相距18km。那么有距離的路我們叫做帶權路徑。根結點為天津的樹,那么第一條路帶權路徑為 3*13 = 39,第二條為2*18。樹的帶權路徑WPL 313+218.
- 哈夫曼樹: 二叉樹是n個結點的結合,它的度(所有孩子個數的最大值)小於等於2。n個結點構成一個二叉樹有許多方法。使二叉樹的帶權路徑最小的樹,我們叫做哈夫曼樹。
- 哈夫曼樹的特點:權值越大,所離根結點越近。
哈夫曼樹有什么用?
- 介紹了這么多概念,不知道它有什么用,讓初學者感覺數據結構沒什么勁。
- 哈夫曼樹主要用在數據的壓縮如JPEG格式圖片,在通信中我們可以先對發送的數據進行哈夫曼編碼壓縮數據提高傳輸速度。
- 查詢優化:在工作中我們我們身邊放許多工具,由於空間限制我們不能把所有工具放在我們最容易拿到的地方,所有我們把使用頻率最高的工具放在最容易的位置。同樣的道理在查詢的時候我們把查詢頻率最高的數據建立索引,這些都是使用了哈夫曼算法的思想。
怎么構造哈夫曼樹?
- 為了理解怎么構造哈夫曼樹我們舉個例子:
- 我們現在有一組字符:{a,b,c,d,e,f,g,h,}
- 他們出現的概率為:{0.19, 0.21, 0.02, 0.03, 0.06, 0.07, 0.1, 0.32}.
- 為了讓我們看起來清楚,我們把它整數化::{19, 21, 2, 3, 6, 7, 10, 32}.
- 即:8個結點的權值大小如下:
- 從19,21,2,3,6,7,10,32中選擇兩個權小結點。選中2,3。同時算出這兩個結點的和5。
- 從19,21,6,7,10,32,5中選出兩個權小結點。選中5,6。同時計算出它們的和11。
- 從19,21,7,10,32,11中選出兩個權小結點。選中7,10。同時計算出它們的和17。
注:這時選出的兩個數字都不是原來的二叉樹里面的結點,所以要另外開一棵二叉樹。
- 從19,21,32,11,17中選出兩個權小結點。選中11,17。同時計算出它們的和28。
- 從19,21,32,28中選出兩個權小結點。選中19,21。同時計算出它們的和40。 另起一顆二叉樹。
- 從32,28, 40中選出兩個權小結點。選中28,32。同時計算出它們的和60。
- 從 40, 60中選出兩個權小結點。選中40,60。同時計算出它們的和100。 好了,此時哈夫曼樹已經構建好了。
構造哈夫曼樹會出現什么問題?
- 在課堂時間的時候我就出現了以問題:當一組數據中最小的兩個數加起來的時候如果出現和原來的一個數據相同怎么辦?
- 還有可能出現這種情況:
- 現在我們來算一下這兩種結果的路徑長度:
- WPL1=(3+5+7+8)*4+(11+14)*3+(23+29)*2=271
- WPL2= (3+5)*+7*4+(8+11+14)*3+(23+29)*2=271
- 經過計算,這兩種方式的路徑長度經計算得到的WPL相等
- 但是,為了得到統一的結果,我們統一將生成相等放到與之相等的后面,也就是說我們生成的結果為第一種。
Huffman算法實現。
- 構造哈夫曼樹結點:
- 結點類我們必須用幾個屬性來完善它,使它能更好地為我們工作:
public class HuffmanTreeNode implements Comparable<HuffmanTreeNode>{ private int weight;//結點所帶的權重,一般為概率的大小 private HuffmanTreeNode parent;//父結點 private HuffmanTreeNode left;//左子結點 private HuffmanTreeNode right;//右子節點 private char element;//元素值 } //CompareTo 方法 用於比較結點之間的權重weight @Override public int compareTo(HuffmanTreeNode huffmanTreeNode) { if (this.weight>huffmanTreeNode.weight) return 1; else{ if (this.weight<huffmanTreeNode.weight) return -1; else return 0; } }
- 結點類我們必須用幾個屬性來完善它,使它能更好地為我們工作:
- 構造哈夫曼樹:
- 首先構造一個根結點以及放26個英文字母的數組:
private HuffmanTreeNode mRoot; // 根結點 private String[] codes = new String[26];
- 然后構造哈夫曼樹:
/** * 構造哈夫曼樹的方法 * @param array 一個數組,里面的數據是元素對應的權值 */ public HuffmanTree(HuffmanTreeNode[] array) { HuffmanTreeNode parent = null; ArrayHeap<HuffmanTreeNode> heap = new ArrayHeap(); for (int i=0;i<array.length;i++) { heap.addElement(array[i]); } for(int i=0; i<array.length-1; i++) { //去除最小的兩個元素生成一棵樹 HuffmanTreeNode left = heap.removeMin(); HuffmanTreeNode right = heap.removeMin(); parent = new HuffmanTreeNode(left.getWeight()+right.getWeight(),' ',null,left,right); left.setParent(parent); right.setParent(parent); heap.addElement(parent); } mRoot = parent; }
- 編寫哈夫曼碼:
- 首先編寫一個中序遍歷的方法,把結點都放在數組中。
protected void inOrder( HuffmanTreeNode node, ArrayList<HuffmanTreeNode> tempList) { if (node != null) { inOrder(node.getLeft(), tempList); if (node.getElement()!=' ') tempList.add(node); inOrder(node.getRight(), tempList); } }
- 然后進行編碼:從下往上,若為左孩子,則為0,反之為1,放入棧中,直至根節點,再將棧中元素全部取出,得到該結點的編碼。
public String[] getEncoding() { ArrayList<HuffmanTreeNode> arrayList = new ArrayList(); inOrder(mRoot,arrayList); for (int i=0;i<arrayList.size();i++) { HuffmanTreeNode node = arrayList.get(i); String result =""; int x = node.getElement()-'a'; Stack stack = new Stack(); while (node!=mRoot) { if (node==node.getParent().getLeft()) stack.push(0); if (node==node.getParent().getRight()) stack.push(1); node=node.getParent(); } while (!stack.isEmpty()) { result +=stack.pop(); } codes[x] = result; } return codes; }
- 哈夫曼解碼:
- 思想:從我們產生的編碼文件中讀取編碼,如果遇到為0,則指向左孩子,遇到1指向右孩子,直到指向葉子節點,即我們要找的結點,然后又從根結點重新開始進行下面的編碼。
- 代碼方法:
for (int i = 0; i < s1.length(); i++) { if (s1.charAt(i) == '0') { if (huffmanTreeNode.getLeft() != null) { huffmanTreeNode = huffmanTreeNode.getLeft(); } } else { if (s1.charAt(i) == '1') { if (huffmanTreeNode.getRight() != null) { huffmanTreeNode = huffmanTreeNode.getRight(); } } } if (huffmanTreeNode.getLeft() == null && huffmanTreeNode.getRight() == null) { result2 += huffmanTreeNode.getElement(); huffmanTreeNode = huffmanTree.getmRoot(); } }
- 寫入文件:
- 方法代碼:
File file2 = new File("F:\\HuffmanCode2.txt"); FileWriter fileWriter1 = new FileWriter(file1); fileWriter1.write(result2);