最優二叉樹(哈夫曼樹)Java實現

王逍遙大人發表於2020-12-24

此篇部落格講最優二叉樹也叫哈夫曼樹的原理,以及構建步驟,還有哈夫曼編碼原理。建議有二叉樹基礎朋友學習交流。對二叉樹基礎可以看我的另外一篇部落格二叉樹的構建以及遍歷

哈夫曼樹引出:

我們平時期末成績判斷,不僅需要給出分數的具體值,還需要給出分數所處的段位,例如優秀、良好、及格、不及格;我們計算機怎樣進行判斷呢?
在這裡插入圖片描述
上面的判斷從最終結果來看並沒有什麼問題,但是我要要考慮效率,當我們每次拿到成績時候都要從是否及格開始一連串的判斷,這樣一定會使判斷效率低下。抽象成判斷樹如下:
在這裡插入圖片描述
一張優秀的試卷我們需要讓大量同學集中在中等或良好的範圍,優秀和不及格同學應該儘量的少。所以這棵樹我們還要考慮各層級學生佔比例問題,所以結點間聯絡要有權重。
在這裡插入圖片描述
如果按照上面程式碼那樣判斷,那麼百分之80的人都需要經過3次以上的判斷。這顯然不合理,所以我們可以考慮下面這樣組織判斷:
在這裡插入圖片描述
從上圖我們能看出大部分人不用經過多次判斷,但是我們是怎樣設計這樣的樹呢?這就是我們赫夫曼樹需要做的,赫夫曼樹也稱為最優二叉樹。

哈夫曼樹原理及實現

定義與原理

在這裡插入圖片描述

  1. 從樹中一個結點到另一個結點間的分支構成兩個結點間的路徑,路徑上的分支數目叫做路徑長度
    例如上圖二叉樹a中從根結點到結點D路徑長度為4,二叉樹b為2。

  2. 樹的路徑長度就是根節點到樹中每一個結點路徑長度之和;
    例如上圖二叉樹a的路徑長度之和為1+1+2+2+3+3+4+4=20。

  3. 考慮帶權的結點,樹的帶權路徑長度為(路勁X權重),只計算帶權的結點,不帶權相當於X0=0;
    例如二叉樹a帶權路徑長度WPL為
    在這裡插入圖片描述
    我們赫夫曼樹就是求WPL最小的樹,既為赫夫曼樹。

哈夫曼樹構造

  • 構造哈夫曼樹演算法步驟
  1. 以權值分別為W1、W2、W3…的n個結點,構成n棵二叉樹T1、T2、T3…Tn(意思就是剛開始預設每棵二叉樹只有一個結點,所以就是n個結點n棵二叉樹),組成森林F={T1、T2、T3…Tn}。
    每顆二叉樹Ti僅有一個權值為Wi的根結點。
  2. 在F中選取兩顆根結點權值最小的樹,作為左右子樹構造一棵新二叉樹,並且置新二叉樹根節點權值為左右子樹根結點權值之和。(根節點權值=左右子樹根結點權值之和,葉子結點權值=Wi)
  3. 從F中刪除這兩棵二叉樹,並將新二叉樹加入到F中,讓它可以和別的剩餘二叉樹一起比較。
  4. 不斷重複2.3兩步,直到F中只有一棵二叉樹為止。
  5. F中最後剩下的二叉樹就是哈夫曼樹。
  • 舉例演示
    假如一篇文章中A出現頻率為27%,27就是結點A的權重,其它的A27,B8,C15,D15,E30,F5;他們加起來就是100%。
    我們用上面構造哈夫曼樹方法來把他們構造成哈夫曼樹如下:
    在這裡插入圖片描述

哈夫曼樹Java程式碼實現

哈夫曼樹的節點類,為了方便使用集合類的排序功能,實現了Comparable介面(可以不是實現該介面,此時需要實現排序功能)

package my.huffmanTree;
 
public class Node<T> implements Comparable<Node<T>> {
	private T data;
	private double weight;
	private Node<T> left;
	private Node<T> right;
	
	public Node(T data, double weight){
		this.data = data;
		this.weight = weight;
	}
	
	public T getData() {
		return data;
	}
 
	public void setData(T data) {
		this.data = data;
	}
 
	public double getWeight() {
		return weight;
	}
 
	public void setWeight(double weight) {
		this.weight = weight;
	}
 
	public Node<T> getLeft() {
		return left;
	}
 
	public void setLeft(Node<T> left) {
		this.left = left;
	}
 
	public Node<T> getRight() {
		return right;
	}
 
	public void setRight(Node<T> right) {
		this.right = right;
	}
 
	@Override
	public String toString(){
		return "data:"+this.data+";weight:"+this.weight;
	}
 
	@Override
	public int compareTo(Node<T> other) {
		if(other.getWeight() > this.getWeight()){
			return 1;
		}
		if(other.getWeight() < this.getWeight()){
			return -1;
		}
		
		return 0;
	}
}

然後:實現哈夫曼樹的主題類,其中包括兩個靜態的泛型方法,為建立哈夫曼樹和廣度優先遍歷哈夫曼樹

package my.huffmanTree;
 
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
 
public class HuffmanTree<T> {
	public static <T> Node<T> createTree(List<Node<T>> nodes){
		while(nodes.size() > 1){
			Collections.sort(nodes);
			Node<T> left = nodes.get(nodes.size()-1);
			Node<T> right = nodes.get(nodes.size()-2);
			Node<T> parent = new Node<T>(null, left.getWeight()+right.getWeight());
			parent.setLeft(left);
			parent.setRight(right);
			nodes.remove(left);
			nodes.remove(right);
			nodes.add(parent);
		}
		return nodes.get(0);
	}
	
	public static <T> List<Node<T>> breadth(Node<T> root){
		List<Node<T>> list = new ArrayList<Node<T>>();
		Queue<Node<T>> queue = new ArrayDeque<Node<T>>();
		
		if(root != null){
			queue.offer(root);
		}
		
		while(!queue.isEmpty()){
			list.add(queue.peek());
			Node<T> node = queue.poll();
			
			if(node.getLeft() != null){
				queue.offer(node.getLeft());
			}
			
			if(node.getRight() != null){
				queue.offer(node.getRight());
			}
		}
		return list;
	}
}

最後:編寫一共測試端

package my.huffmanTree;
 
import java.util.ArrayList;
import java.util.List;
 
public class Test {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		List<Node<String>> list = new ArrayList<Node<String>>();
		list.add(new Node<String>("a",7));
		list.add(new Node<String>("b",5));
		list.add(new Node<String>("c",4));
		list.add(new Node<String>("d",2));
		
		Node<String> root = HuffmanTree.createTree(list);
		System.out.println(HuffmanTree.breadth(root));
//		System.out.println(list);
	}
}

相關文章