簡介
-
哈弗曼編碼:又稱爲霍夫曼編碼,它是現代壓縮算法的基礎。
-
假設要把字符串【ABBBCCCCCCCCDDDDDDEE】轉成二進制編碼進行傳輸
-
可以轉成ASCII編碼(65-69,01000001-01000101),但是有點冗長,如果希望編碼更短呢?
-
我們可以先自己約定5個字母對應二進制碼
A B C D E 000 001 010 011 100 -
對應的二進制編碼: 000001001001010010010010010010010010011011011011011100100
-
一共20個字母,轉成了60個二進制位
-
如果使用哈弗曼編碼,可以壓縮至41個二進制位,約爲原來長度的68.3%
-
-
先計算出每個字母的出現頻率(權值,這裏直接用出現次數),【ABBBCCCCCCCCDDDDDDEE】
A | B | C | D | E |
---|---|---|---|---|
1 | 3 | 8 | 6 | 2 |
-
利用這些權值,構建一棵哈弗曼樹(又稱霍夫曼樹、最優二叉樹)
構建哈夫曼樹
-
如何構建一棵哈弗曼樹?(假設有n個權值)
-
以權值作爲根節點構建n棵二叉樹,組成森林
-
在森林中選出兩個根節點最小的樹合併,作爲一棵新樹的左右子樹,且新樹的根結點權值爲其左右子樹的根結點之和
-
從森林中刪除剛纔選取的兩棵樹,並將新樹加入森林
-
重複2,3步驟,直到森林只剩一棵樹爲止,該樹即爲哈弗曼樹
構建哈夫曼編碼
-
left爲0,right爲1,可以得出5個字母對應的哈弗曼編碼
A | B | C | D | E |
---|---|---|---|---|
1110 | 110 | 0 | 10 | 1111 |
-
【ABBBCCCCCCCCDDDDDDEE】的哈弗曼編碼是
111011011011000000000101010101011111111
-
總結
-
n個權值構建出來的哈弗曼樹擁有n個葉子節點
-
每個哈弗曼編碼都不是另一個哈弗曼編碼的前綴
-
哈弗曼樹是帶權路徑長度最短的樹,權值較大的節點離根較近
-
帶權路徑長度:樹中所有葉子節點的權值乘上其到根節點的路徑長度,與最終的哈弗曼編碼總長度成正比關係。
-
假設我們一個權重爲1,7,3,13,12,15,24,30怎麼樣畫出哈夫曼樹和計算帶權路徑長度。
首先,選出最小的兩個權重值,這裏是1,3(矩形表示葉子節點,圓表示根節點也是兩個葉子節點的和)如圖:
然後,選出第三小的7,算出父節點,如圖:
依次類推:
當11,12的父節點爲23大於後面的13 ,重新畫一個分支,如圖:
這樣一棵哈夫曼樹就構建好了。
計算帶權路徑計算:
其實計算葉子節點的到根節點的距離乘以葉子節點自身的值,然後相加:
例如1這個葉子節點,1*5=5,3節點,3*5=15
1*5 + 3*5 + 7*4 + 12*3 + 30*2 + 13*2 + 14*2 = 188
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
public class HuffmanTree {
//節點類
public static class Node<E>{
E data;
double weight; //權重
Node<E> leftChild;
Node<E> rightChild;
public Node(E data,double weight){
this.data=data;
this.weight=weight;
}
public String toString(){
return "Node[data="+data+",weight="+weight+"]";
}
}
private static<E> Node<E> createTree(List<Node<E>> nodes){
//只要nodes數組中有兩個以上的節點
while(nodes.size()>1){
quickSort(nodes);
//獲取權值最小的兩個節點
Node<E> left=nodes.get(nodes.size()-1);
Node<E> right=nodes.get(nodes.size()-2);
//生成新節點,新節點權值爲兩個子節點之和
Node<E> parent=new Node<>(null,left.weight+right.weight);
//讓新節點作爲權值最小的兩個節點的父節點
parent.leftChild=left;
parent.rightChild=right;
//刪除權值最小的兩個節點
nodes.remove(nodes.size()-1);
nodes.remove(nodes.size()-1);
//將新生成的父節點添加到集合中
nodes.add(parent);
}
return nodes.get(0);
}
private static<E> void swap(List<Node<E>> nodes,int i,int j){
Node<E> tmp;
tmp=nodes.get(i);
nodes.set(i,nodes.get(j));
nodes.set(j,tmp);
}
//實現快速排序算法,用於對節點進行排序(用其他的排序算法也可)
private static <E> void subSort(List<Node<E>> nodes,
int start,int end){
if(start<end){
Node base=nodes.get(start);
int i=start;
int j=end+1;
while(true){
while(i<end&&nodes.get(++i).weight>=base.weight);
while(j>start&&nodes.get(--j).weight<=base.weight);
if(i<j){
swap(nodes,i,j);
}else{
break;
}
}
swap(nodes,start,j);
subSort(nodes,start,j-1);
subSort(nodes,j+1,end);
}
}
public static<E> void quickSort(List<Node<E>> nodes){
subSort(nodes,0,nodes.size()-1);
}
public static List<Node> breadthFirst(Node root){
Queue<Node> queue=new ArrayDeque<Node>();
List<Node> list=new ArrayList<Node>();
if(root!=null){
queue.offer(root);
}
while(!queue.isEmpty()){
list.add(queue.peek());
Node p=queue.poll();
if(p.leftChild!=null){
queue.offer(p.leftChild);
}
if(p.rightChild!=null){
queue.offer(p.rightChild);
}
}
return list;
}
public static void main(String[] args) {
List<Node<String>> nodes=new ArrayList<>();
nodes.add(new Node<String>("A",7.0));
nodes.add(new Node<String>("B",5.0));
nodes.add(new Node<String>("C",2.0));
nodes.add(new Node<String>("D",4.0));
Node<String> root=HuffmanTree.createTree(nodes);
List<Node> list=breadthFirst(root);
for(Node l:list){
System.out.println(l);
}
}
}