目錄
- 哈夫曼樹的構造
- 哈夫曼編碼的構造
哈夫曼樹的構造
首先給出樹的幾個概念:
- 路徑:從樹種一個結點到另一個結點之間的分支構成這兩個結點之間的路徑,路徑上的分支數目稱作路徑長度。
- 樹的路徑長度:從樹根到每一個結點的路徑長度之和。
- 結點的帶權路徑長度:爲從該結點到樹根的之間的路徑長度與結點上權的成績。
- 樹的帶權路徑長度:爲數中所有葉子結點的帶權路徑長度之和。
- 構建哈夫曼樹的思想其實就是貪心的思想,每次在目前可選的樹結點中,選取兩個最小的結點,構造成一顆新的二叉樹,直到只有一棵樹爲止。
哈夫曼編碼的構造
那麼如何構造出最優的前綴編碼,也就是赫夫曼編碼呢?
注意這裏的huffCodes存儲的哈夫曼編碼的技巧:
- 首先我是從每一個葉子從下往上去構造哈夫曼編碼;
- 然後我的數組也是從每一行的最後一個開始構造(idx = n - 1) ;
- 也就是說每一個一維數組內,我是從最後開始存,存的是從葉子到根的,存完之後,加上一個-1,然後我輸出的時候,從數組的前面開始,碰到-1,就說明編碼的開始(也就是說譯碼其實是從根到葉子的過程,但是這裏是從葉子到根構建的);
看下圖:
代碼如下:
import java.util.LinkedList;
import java.util.Queue;
public class HuffmanTree {
private static class Node {
private int val;
private boolean flag; //標記是否已經被選
private Node parent; // 父親結點
private Node left;
private Node right;
public Node(int val) {
this(val, false , null, null, null);
}
public Node(int val, boolean flag, Node parent, Node left, Node right) {
super();
this.val = val;
this.flag = flag;
this.parent = parent;
this.left = left;
this.right = right;
}
}
//每次選出一個最小的結點的函數
public static Node selectMin(Node[] HN,int end){ //找到最小的一個結點
Node minNode = HN[end];
for(int i = 0; i <= end; i++){
if(HN[i].flag == false && HN[i].val < minNode.val){
minNode = HN[i];
}
}
return minNode;
}
//建立赫夫曼樹
public static Node[] build(int[] w){
int m = w.length * 2 - 1;
Node[] HN = new Node[m+1];
for(int i = 0; i < w.length; i++) //先建立好前面的葉子結點
HN[i] = new Node(w[i]);
/**
* 已經有w.length - 1個葉子結點,現在要建立 從w.length ~ (2*w.length-1) 的結點
*/
for(int i = w.length; i < m; i++){
Node firstNode = selectMin(HN, i-1); //從前面的結點中找到第一個最小的
firstNode.flag = true;
Node secondNode = selectMin(HN, i-1); //從前面的結點中找到第二個最小的
secondNode.flag = true;
HN[i] = new Node(firstNode.val + secondNode.val); //新結點的值是兩個最小結點的和
//設置好孩子和孩子的父親
HN[i].left = firstNode;
HN[i].right = secondNode;
firstNode.parent = HN[i];
secondNode.parent = HN[i];
}
return HN; //返回結點數組
}
public static void leverOrder(Node root){
if(root == null)
return;
Queue<Node> que = new LinkedList<Node>();
que.add(root);
while(!que.isEmpty()){
Node cur = que.poll();
System.out.print(cur.val + " ");
if(cur.left != null)
que.add(cur.left);
if(cur.right != null)
que.add(cur.right);
}
System.out.println();
}
//哈夫曼編碼 : 從葉子到根的一個路徑
public static int[][] huffmanCoding(Node[] HN, int n){
int[][] huffCodes = new int[n][n];
for(int i = 0; i < n; i++){
int idx = n-1; //逆向存儲
for(Node cur = HN[i], pa = cur.parent ; pa != null; cur = pa,pa = pa.parent){
if(pa.left == cur) //左孩子爲0
huffCodes[i][idx--] = 0;
else //右孩子爲1
huffCodes[i][idx--] = 1;
}
huffCodes[i][idx--] = -1; //標記一下 從-1往後的是真的編碼 前面的不足
}
return huffCodes;
}
public static void main(String[] args) {
int[] w = {7,5,2,4}; // 給出葉子的權值數組
Node[] HN = build(w);
System.out.println("-----哈夫曼樹-----");
leverOrder(HN[2 * w.length - 2]); //最後的根 的下標
int[][] huffCodes = huffmanCoding(HN,w.length); //開始從葉子到根構建編碼
System.out.println("----哈夫曼編碼-----");
for(int i = 0; i < w.length; i++){
System.out.print(w[i] + ": ");
for(int j = 0; j < huffCodes[i].length; j++){
if(huffCodes[i][j] == -1){ //從-1標記的開始往後的纔是編碼
for(int k = j + 1; k < huffCodes[i].length; k++)
System.out.print(huffCodes[i][k]);
break;
}
}
System.out.println();
}
}
}
運行效果:
對照上面的例子,可以看出來是對的。
另一種寫法: 使用ArrayList存儲哈夫曼編碼:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
public class HuffmanTree2 {
private static class Node {
private int val;
private boolean flag; //標記是否已經被選
private Node parent; // 父親結點
private Node left;
private Node right;
public Node(int val) {
this(val, false , null, null, null);
}
public Node(int val, boolean flag, Node parent, Node left, Node right) {
super();
this.val = val;
this.flag = flag;
this.parent = parent;
this.left = left;
this.right = right;
}
}
//每次選出一個最小的結點的函數
public static Node selectMin(Node[] HN,int end){ //找到最小的一個結點
Node minNode = HN[end];
for(int i = 0; i <= end; i++){
if(HN[i].flag == false && HN[i].val < minNode.val){
minNode = HN[i];
}
}
return minNode;
}
//建立赫夫曼樹
public static Node[] build(int[] w){
int m = w.length * 2 - 1;
Node[] HN = new Node[m+1];
for(int i = 0; i < w.length; i++) //先建立好前面的葉子結點
HN[i] = new Node(w[i]);
/**
* 已經有w.length - 1個葉子結點,現在要建立 從w.length ~ (2*w.length-1) 的結點
*/
for(int i = w.length; i < m; i++){
Node firstNode = selectMin(HN, i-1); //從前面的結點中找到第一個最小的
firstNode.flag = true;
Node secondNode = selectMin(HN, i-1); //從前面的結點中找到第二個最小的
secondNode.flag = true;
HN[i] = new Node(firstNode.val + secondNode.val); //新結點的值是兩個最小結點的和
//設置好孩子和孩子的父親
HN[i].left = firstNode;
HN[i].right = secondNode;
firstNode.parent = HN[i];
secondNode.parent = HN[i];
}
return HN; //返回結點數組
}
public static void leverOrder(Node root){
if(root == null)
return;
Queue<Node> que = new LinkedList<Node>();
que.add(root);
while(!que.isEmpty()){
Node cur = que.poll();
System.out.print(cur.val + " ");
if(cur.left != null)
que.add(cur.left);
if(cur.right != null)
que.add(cur.right);
}
System.out.println();
}
//哈夫曼編碼 : 從葉子到根的一個路徑
public static ArrayList[] huffmanCoding(Node[] HN, int n){
ArrayList[] huffCodes = new ArrayList[n];
for(int i = 0; i < huffCodes.length; i++)
huffCodes[i] = new ArrayList<Integer>();
for(int i = 0; i < n; i++){
for(Node cur = HN[i], pa = cur.parent ; pa != null; cur = pa,pa = pa.parent){
if(pa.left == cur) //左孩子爲0
huffCodes[i].add(0);
else //右孩子爲1
huffCodes[i].add(1);
}
}
return huffCodes;
}
public static void main(String[] args) {
int[] w = {7,5,2,4}; // 給出葉子的權值數組
Node[] HN = build(w);
System.out.println("-----哈夫曼樹-----");
leverOrder(HN[2 * w.length - 2]); //最後的根 的下標
ArrayList[] huffCodes = huffmanCoding(HN,w.length); //開始從葉子到根構建編碼
System.out.println("----哈夫曼編碼-----");
for(int i = 0; i < w.length; i++){
System.out.print(w[i] + ": ");
for(int j = huffCodes[i].size() - 1; j >= 0; j--)
System.out.print(huffCodes[i].get(j));
System.out.println();
}
}
}