叫聲小哥哥,帶你去看美麗的Java數據結構之哈夫曼樹

簡介

  • 哈弗曼編碼:又稱爲霍夫曼編碼,它是現代壓縮算法的基礎。

  • 假設要把字符串【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個權值)

    1. 以權值作爲根節點構建n棵二叉樹,組成森林

 

在森林中選出兩個根節點最小的樹合併,作爲一棵新樹的左右子樹,且新樹的根結點權值爲其左右子樹的根結點之和

  1. 從森林中刪除剛纔選取的兩棵樹,並將新樹加入森林

  2. 重複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);
		}
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章