基本概念:(1)一棵二叉樹中定義,從A結點到B結點所經過的分支序列叫A到B的路徑,所經過的分支個數叫路徑長度;(2)從二叉樹的根結點到二叉樹中所有葉結點的路徑長度之和稱爲該二叉樹的路徑長度。
若葉結點帶有權值,設二叉樹有n個帶權值葉結點,定義從根結點到二叉樹中所有葉結點的路徑長度與相應葉結點權值的乘積之和爲該二叉樹的帶權路徑長度(WPL)。那麼,對於一組具有確定權值的葉結點,可以構造出多個具有不同帶權路徑長度的二叉樹,其中具有最小帶權路徑長度的二叉樹稱作哈夫曼樹。
根據哈夫曼樹定義,要想使二叉樹WPL值最小,必須使權值越大的葉結點越靠近根結點,其構造過程如下:
(1)由給定的n個權值{w1,w2,,...,wn}構造n棵只有根結點的二叉樹,其實就是n個二叉樹散結點互不相連,從而得到一個二叉樹森林F。
(2)在二叉樹森林F中選根結點權值最小和次小的兩棵樹作爲新的二叉樹的左右子樹來構造出新的二叉樹,新二叉樹的根結點權值爲左右子樹根結點權值之和。
(3)在森林F中刪除作爲新二叉樹左右子樹的兩棵二叉樹,將新的二叉樹加入森林F。
(4)重複步驟2和步驟3,當森林F中只剩下一棵二叉樹時,即爲所構造的哈夫曼樹。
如下圖:
哈夫曼編碼:
哈夫曼樹可用於構造代碼總長度最短的編碼方案。具體爲:以需要編碼的字符爲葉結點,各個字符出現的次數爲葉結點權值來構造哈夫曼樹,規定哈夫曼樹中左分支爲0,右分支爲1,那麼從根結點到每個葉結點所經歷的分支對應的0和1組成的序列便爲該葉結點(字符)的編碼。
哈夫曼樹的結點存儲結構採用雙親孩子存儲結構,仿真指針實現。每個結點包括雙親,左右孩子,權值域和標誌域5個域,標誌flag爲0說明該結點尚未加入到哈夫曼樹,爲1說明已經加入到哈夫曼樹。
weight | flag | parent | leftChild | rightChild |
哈夫曼結點類:
package Tree;
/**
* @author sun
* 創建時間:2017年5月3日下午3:23:05
*/
//建立基於雙親孩子仿真指針存儲結構的哈夫曼結點類
public class HaffNode {
int weight;//權值
int flag;//標記
int parent;//雙親結點下標
int leftChild;//左孩子下標
int rightChild;//右孩子下標
public HaffNode(){}
}
哈夫曼編碼類:
package Tree;
/**
* @author sun
* 創建時間:2017年5月3日下午3:25:55
*/
//建立保存哈夫曼編碼的哈夫曼編碼類
public class Code {
int[] bit;//編碼用數組
int start;//編碼的起始下標
int weight;//字符的權值
public Code(int n){
bit = new int[n];
start = n-1;
}
}
哈夫曼樹類:
package Tree;
/**
* @author sun
* 創建時間:2017年5月3日下午3:29:33
*/
//構造哈夫曼樹和哈夫曼編碼的哈夫曼樹類
public class HaffmanTree {
static final int maxValue = 10000;//最大權值
private int nodeNum;//葉子結點個數
public HaffmanTree(int n){
nodeNum = n;
}
public void haffman(int[] weight,HaffNode[] node){
//構造權值爲weight的哈夫曼樹
int m1,m2,x1,x2;
int n = nodeNum;
//哈夫曼樹初始化,n個葉結點的哈夫曼樹共有2n-1個結點
for(int i=0;i<2*n-1;i++){
HaffNode temp = new HaffNode();
if(i<n)
temp.weight = weight[i];
else
temp.weight = 0;
temp.parent = -1;
temp.flag = 0;
temp.leftChild = -1;
temp.rightChild = -1;
node[i] = temp;
}
//構造哈夫曼樹的n-1個非葉結點,取出最小的兩棵樹合併
for(int i=0;i<n-1;i++){
m1 = m2 = maxValue;
x1 = x2 = 0;//x1爲最小樹,x2爲次小樹
for(int j=0;j<n+i;j++){
if(node[j].weight<m1 && node[j].flag==0){
m2 = m1;
x2 = x1;
m1 = node[j].weight;
x1 = j;
}
else if(node[j].weight<m2 && node[j].flag==0){
m2 = node[j].weight;
x2 = j;
}
}
//將找出的兩棵權值最小的子樹合併爲一棵子樹
node[x1].parent = n+i;
node[x2].parent = n+i;
node[x1].flag = 1;
node[x2].flag = 1;
node[n+i].weight = node[x1].weight+node[x2].weight;
node[n+i].leftChild = x1;
node[n+i].rightChild = x2;
}
}
public void haffmanCode(HaffNode[] node,Code[] haffCode){
//由哈夫曼樹構造哈夫曼編碼
int n = nodeNum;
Code cd = new Code(n);
int child,parent;
//求n個葉結點的哈夫曼編碼
for(int i=0;i<n;i++){
cd.start = n-1;//不等長編碼的最後一位爲n-1
cd.weight = node[i].weight;//取得編碼對應的權值
child = i;
parent = node[child].parent;
while(parent!=-1){
//由葉結點向上直到根結點循環
if(node[parent].leftChild == child)
cd.bit[cd.start] = 0;//左孩子結點編碼爲0
else
cd.bit[cd.start] = 1;//右孩子結點編碼1
cd.start--;
//System.out.print(cd.start+" ");
child = parent;
parent = node[child].parent;
}
Code temp = new Code(n);
//保存葉結點的編碼和不等長編碼的起始位
for(int j=cd.start+1;j<n;j++)
temp.bit[j] = cd.bit[j];
temp.start = cd.start;
temp.weight = cd.weight;
haffCode[i] = temp;
}
}
}
測試:
package Tree;
/**
* @author sun
* 創建時間:2017年5月3日下午3:55:33
*/
//設有字符集{A,B,C,D},各字符在電文中出現次數集爲{1,3,5,7}設計各字符的哈夫曼編碼
public class TestHaffman {
public static void main(String[] args) {
int n = 4;
HaffmanTree myHaff = new HaffmanTree(n);
int[] weight = {1,3,5,7};
HaffNode[] node = new HaffNode[2*n-1];
Code[] haffCode = new Code[n];
myHaff.haffman(weight, node);
myHaff.haffmanCode(node, haffCode);
for(int i=0;i<n;i++){
System.out.print("Weight = "+haffCode[i].weight+"Code = ");
for(int j=haffCode[i].start+1;j<n;j++){
System.out.print(haffCode[i].bit[j]);
}
System.out.println();
}
}
}
/*
Weight = 1Code = 100
Weight = 3Code = 101
Weight = 5Code = 11
Weight = 7Code = 0
*/