哈夫曼樹,又稱最優二叉樹。特點是每一個(葉子)節點都有一個權值,可以認爲代表的是這個點被查詢的概率。哈夫曼樹要求所有(葉子節點)權值*深度的和最短。
爲了說明方便,設節點的值與權值相等。
哈夫曼樹的構建
通過觀察可以看出,權值小的在下,大的在上。由此可以很容易理解構建的規則:
將所有的節點(或者說是隻有根節點的子樹)放入一個集合,每次取出兩個最小的節點合成一個新子樹並放入集合,循環,直到集合中只剩最後一個元素。
jdk已經提供了優先隊列的實現,所以實現起來非常簡單
創建測試代碼,創建節點類
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
public class HuffmanTree {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Integer> val = new ArrayList<Integer>();
val.add(2);
val.add(4);
val.add(5);
val.add(7);
HuffmanTree tree = new HuffmanTree();
Node node = tree.init(val);
}
private class Node{
Node left;
Node right;
Integer value = null;//null代表無值節點
Integer weight;
boolean isLeaf;//是否爲葉子節點,所有實際值都在葉子節點上
public Node(Node left, Node right, Integer value, Integer weight, boolean isLeaf) {
super();
this.left = left;
this.right = right;
this.value = value;
this.weight = weight;
this.isLeaf = isLeaf;
}
}
}
哈夫曼樹構建
//構建哈夫曼樹
public Node init(List<Integer> val) {
Queue<Node> queue = new PriorityQueue<Node>((n1, n2) -> n1.weight - n2.weight);
val.stream().forEach(v -> queue.add(new Node(null, null, v, v, true)));
while(queue.size() > 1) {
Node n1 = queue.poll();
Node n2 = queue.poll();
queue.add(new Node(n1, n2, null, n1.weight + n2.weight, false));
}
return queue.poll();
}
查找
網上相關資料比較少,只看到有一篇文章說了要用廣度優先遍歷。由於權值低的點深度都會相對較低,所以這應該是正確的。
查找
public boolean has(Node node, Integer value) {
Queue<Node> queue = new LinkedList();
queue.add(node);
while(queue.size() > 0) {
Node temp = queue.poll();
if(temp.isLeaf && temp.value == value) {
return true;
}else {
if(temp.left != null)
queue.add(temp.left);
if(temp.right != null)
queue.add(temp.right);
}
}
return false;
}
生成哈夫曼編碼
從根節點到目標節點,按照依次經過的是左子樹還是右子樹,分別填0或1 。
一個很直觀的方法是,遍歷每一個葉子節點,再從下向上逆向輸出哈夫曼編碼。但是這就要求節點爲雙向的,改起來有點麻煩。仔細一想,只要用前序遍歷改一下就可以了
public void printHuffmanCode(Node node) {
Stack<Integer> stack = new Stack();
recHuffmanCode(node, stack);
}
private void recHuffmanCode(Node node, Stack<Integer> stack) {
if(node == null)return;
if(node.isLeaf) {
System.out.println(node.value + ": " + stack);
}
stack.add(0);
recHuffmanCode(node.left, stack);
stack.pop();
stack.add(1);
recHuffmanCode(node.right, stack);
stack.pop();
}
經過測試後發現,其實中序和後序也是可行的
中序
private void recHuffmanCode_mid(Node node, Stack<Integer> stack) {
if(node == null)return;
stack.add(0);
recHuffmanCode(node.left, stack);
stack.pop();
if(node.isLeaf) {
System.out.println(node.value + ": " + stack);
}
stack.add(1);
recHuffmanCode(node.right, stack);
stack.pop();
}
最後附上完整代碼
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Stack;
public class HuffmanTree {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Integer> val = new ArrayList<Integer>();
val.add(2);
val.add(4);
val.add(5);
val.add(7);
// val.add(10);
// val.add(11);
// val.add(12);
// val.add(13);
HuffmanTree tree = new HuffmanTree();
Node node = tree.init(val);
tree.prevPrint(node);
System.out.println(tree.has(node, 8));
tree.printHuffmanCode(node);
}
public void printHuffmanCode(Node node) {
Stack<Integer> stack = new Stack();
recHuffmanCode_mid(node, stack);
}
private void recHuffmanCode(Node node, Stack<Integer> stack) {
if(node == null)return;
if(node.isLeaf) {
System.out.println(node.value + ": " + stack);
}
stack.add(0);
recHuffmanCode(node.left, stack);
stack.pop();
stack.add(1);
recHuffmanCode(node.right, stack);
stack.pop();
}
private void recHuffmanCode_mid(Node node, Stack<Integer> stack) {
if(node == null)return;
stack.add(0);
recHuffmanCode(node.left, stack);
stack.pop();
if(node.isLeaf) {
System.out.println(node.value + ": " + stack);
}
stack.add(1);
recHuffmanCode(node.right, stack);
stack.pop();
}
public Node init(List<Integer> val) {
Queue<Node> queue = new PriorityQueue<Node>((n1, n2) -> n1.weight - n2.weight);
val.stream().forEach(v -> queue.add(new Node(null, null, v, v, true)));
while(queue.size() > 1) {
Node n1 = queue.poll();
Node n2 = queue.poll();
queue.add(new Node(n1, n2, null, n1.weight + n2.weight, false));
}
return queue.poll();
}
public void prevPrint(Node node) {
if(node == null)return;
System.out.println("value: " + node.value + ", weight: " + node.weight);
prevPrint(node.left);
prevPrint(node.right);
}
public boolean has(Node node, Integer value) {
Queue<Node> queue = new LinkedList();
queue.add(node);
while(queue.size() > 0) {
Node temp = queue.poll();
if(temp.isLeaf && temp.value == value) {
return true;
}else {
if(temp.left != null)
queue.add(temp.left);
if(temp.right != null)
queue.add(temp.right);
}
}
return false;
}
private class Node{
Node left;
Node right;
Integer value = null;//null代表無值節點
Integer weight;
boolean isLeaf;//是否爲葉子節點,所有實際值都在葉子節點上
public Node(Node left, Node right, Integer value, Integer weight, boolean isLeaf) {
super();
this.left = left;
this.right = right;
this.value = value;
this.weight = weight;
this.isLeaf = isLeaf;
}
}
}