1.定義:給定n個權值做爲n個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹爲最優二叉樹,也稱爲哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。
2.術語html
(01) 路徑和路徑長度
定義:在一棵樹中,從一個結點往下能夠達到的孩子或孫子結點之間的通路,稱爲路徑。通路中分支的數目稱爲路徑長度。若規定根結點的層數爲1,則從根結點到第L層結點的路徑長度爲L-1。
例子:100和80的路徑長度是1,50和30的路徑長度是2,20和10的路徑長度是3。node
(02) 結點的權及帶權路徑長度
定義:若將樹中結點賦給一個有着某種含義的數值,則這個數值稱爲該結點的權。結點的帶權路徑長度爲:從根結點到該結點之間的路徑長度與該結點的權的乘積。
例子:節點20的路徑長度是3,它的帶權路徑長度= 路徑長度 * 權 = 3 * 20 = 60。git
(03) 樹的帶權路徑長度
定義:樹的帶權路徑長度規定爲全部葉子結點的帶權路徑長度之和,記爲WPL。
例子:示例中,樹的WPL= 1100 + 280 + 320 + 310 = 100 + 160 + 60 + 30 = 350。
3.構造
假設有n個權值,則構造出的哈夫曼樹有n個葉子結點。 n個權值分別設爲 w一、w二、…、wn,則哈夫曼樹的構造規則爲:數組
(1) 將w一、w二、…,wn當作是有n 棵樹的森林(每棵樹僅有一個結點);ide
(2) 在森林中選出兩個根結點的權值最小的樹合併,做爲一棵新樹的左、右子樹,且新樹的根結點權值爲其左、右子樹根結點權值之和;
(3)從森林中刪除選取的兩棵樹,並將新樹加入森林;
(4)重複(2)、(3)步,直到森林中只剩一棵樹爲止,該樹即爲所求得的哈夫曼樹。函數
public class HuffmanTreeNode implements Comparable<HuffmanTreeNode> { private int weight; private HuffmanTreeNode parent,left,right; private char element; public HuffmanTreeNode(int weight, char element ,HuffmanTreeNode parent, HuffmanTreeNode left, HuffmanTreeNode right) { this.weight = weight; this.element = element; this.parent = parent; this.left = left; this.right = right; } @Override public int compareTo(HuffmanTreeNode huffmanTreeNode) { return this.weight - huffmanTreeNode.getWeight(); }
1.定義一個根節點
private HuffmanTreeNode mRoot; // 根結點
2.構造函數
這裏將會使用一個最小堆,用來每次取出權值最小的兩個哈夫曼樹結點來構形成一個新的哈夫曼樹結點,並添加到最小堆中去。直到全部元素構形成一個哈夫曼樹結點,設爲根節點。this
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; }
編碼的方法寫在了哈夫曼樹的構造中。
採用了中序遍歷把全部葉子結點都添加到一個數組中來,而後對這些葉子結點逐個進行編碼,從下往上,若爲左孩子,則爲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; } 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,則指向結點左孩子,反之爲其右孩子,若是其沒有左右孩子,便爲咱們所找的葉子結點,將其對應元素添加進來,並從新從根結點開始,直至讀取完畢。設計
//進行解碼 String result2 = ""; 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(); } }