哈夫曼树又称最优树(二叉树),是一类带权路径最短的树。构造这种树的算法最早是由哈夫曼(Huffman)1952年提出,这种树在信息检索中很有用。
结点之间的路径长度:从一个结点到另一个结点之间的分支数目。
树的路径长度:从树的根到树中每一个结点的路径长度之和。
结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的乘积。
树的带权路径长度:树中所有叶子结点的带权路径长度之和,记作:
WPL为最小的二叉树就称作最优二叉树或哈夫曼树。
完全二叉树不一定是最优二叉树。
哈夫曼树的构造:
(1)根据给定的n个权值{w1,w2,...,wn}构造n棵二叉树的集合F={T1,T2,...,Tn},其中Ti中只有一个权值为wi的根结点,左右子树为空;
(2)在F中选取两棵根结点的权值为最小的数作为左、右子树以构造一棵新的二叉树,且置新的二叉树的根结点的权值为左、右子树上根结点的权值之和。
(3)将新的二叉树加入到F中,删除原两棵根结点权值最小的树;
(4)重复(2)和(3)直到F中只含一棵树为止,这棵树就是哈夫曼树。
例1:
例2:
结点的存储结构:
构造哈夫曼树的算法说明:
#define n /* 叶子总数 */
#define m 2*n-1 /* 结点总数 */
证:由性质3,叶子结点数 n0=n2+1,故哈夫曼树结点总数为 n0+n2=n0+(n0-1)=2*n0-1
例3 在解某些判定问题时,利用哈夫曼树获得最佳判定算法。
(a)
WPL=0.05*1+0.15*2+0.4*3+0.3*4+0.1*4=3.15
(b)(c)
WPL=0.4*1+0.3*2+0.15*3+0.05*4+0.1*4=2.05 WPL=0.05*3+0.15*3+0.4*2+0.3*2+0.1*2=2.2
哈夫曼编码
从哈夫曼树根结点开始,对左子树分配代码“0”,右子树分配代码“1”,一直到达叶子结点为止,然后将从树根沿每条路径到达叶子结点的代码排列起来,便得到了哈夫曼编码。
例,对电文 EMCAD 编码。若等长编码,则
EMCAD => 000001010011100 共15位
设各字母的使用频度为 {E,M,C,A,D}={1,2,3,3,4}。用频度为权值生成哈夫曼树,并在叶子上标注对应的字母,树枝分配代码“0”或“1”:
各字母的编码即为哈夫曼编码: EMCAD => 000001011011 共12位
package arg;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
public class HuffmanCode1 extends Tree {
public HuffmanCode1() {
init();
}
/**
* 初始化节点值并构造最优二叉树
*
*/
public void init() {
super.getLeafWeight();
super.makeBestTree();
}
/**
* 生成赫夫曼编码的递归函数
*
* @param t
* TNode 当前遍历节点
* @param s
* String 目前遍历至此的赫夫曼编码
*/
protected void hufT(TNode t, String s) {
if (t.isLeaf()) {
t.setHuffCode(s);
} else {
hufT(t.lChild, s + "0");
hufT(t.rChild, s + "1");
}
}
/**
* 生成赫夫曼编码的外部调用函数
*
*/
public void makeHuffCode() {
hufT(root, "");
}
/**
* 查看所有的赫夫曼编码值
*
*/
public void viewHuffCode() {
for (int i = 0; i < leafArr.length; i++) {
System.out.println(leafArr[i].w + ":" + leafArr[i].getHuffCode());
}
}
public static void main(String[] args) {
HuffmanCode1 hc = new HuffmanCode1();
hc.makeHuffCode();
hc.viewHuffCode();
}
}
class TNode {
/** 权值 */
protected int w;
/** 左孩子节点 */
TNode lChild = null;
/** 右孩子节点 */
TNode rChild = null;
/** (仅对叶子节点有效)赫夫曼编码 */
String huffCode = null;
/**
* 构造一个赫夫曼编码节点的实例。设定左右子树和权值
*
* @param w
* int 权值
* @param l
* TNode 左孩子节点
* @param r
* TNode 右孩子节点
*/
public TNode(int w, TNode l, TNode r) {
this.w = w;
lChild = l;
rChild = r;
}
/**
* 构造一个赫夫曼编码叶子点的实例。仅仅设定权值
*
* @param w
* int 权值
*/
public TNode(int w) {
this.w = w;
}
/**
* 判断本节点是否为叶子节点
*
* @return boolean 为叶子节点则返回true
*/
public boolean isLeaf() {
return (rChild == null && lChild == null);
}
/**
* 返回本节点的权值
*
* @return int 本节点的权值
*/
public int getWeight() {
return w;
}
/**
* 返回本节点的左孩子节点
*
* @return TNode 左孩子节点
*/
public TNode leftChild() {
return lChild;
}
/**
* 返回本节点的右孩子节点
*
* @return TNode 右孩子节点
*/
public TNode rightChild() {
return rChild;
}
/**
* 设置节点的赫夫曼编码
*
* @param str
* String 被设置的赫夫曼编码
*/
public void setHuffCode(String str) {
huffCode = str;
}
/**
* 得到节点的赫夫曼编码
*
* @return String 被设置的赫夫曼编码
*/
public String getHuffCode() {
return huffCode;
}
}
/**
*
最优二叉树类
*
*
* @version 1.0, 2007-01-8
* @author 李赫元
* @since JDK1.6
*/
class Tree {
/** 最优二叉树的根节点 */
protected TNode root;
/** 存储叶子节点的权值 */
protected List<Integer> leafWList = new ArrayList<Integer>();
/** 临时队列,用于存放待组合的节点 */
protected List<TNode> tmpList = new LinkedList<TNode>();
/** 存放带权节点 */
protected TNode[] leafArr = null;
/**
* 从键盘读取各个权值
*
*/
public void getLeafWeight() {
Scanner scan = new Scanner(System.in);
System.out.println("请输入各叶子节点的权值,0为结束:");
while (scan.hasNextInt()) {
int i = scan.nextInt();
if (i == 0)
break;
leafWList.add(new Integer(i));
}
scan = null;
return;
}
/**
* 找出临时队列中权值最小的节点从队列中移出并返回
*
* @return TNode 权值最小的节点
*/
public TNode min() {
Iterator<TNode> itr = tmpList.iterator();
TNode minNode = itr.next();
int min = minNode.getWeight();
// 找到最小的节点
TNode tmpNode;
while (itr.hasNext()) {
tmpNode = itr.next();
if (tmpNode.getWeight() < min) {
min = tmpNode.getWeight();
minNode = tmpNode;
}
}
// 最小的节点移出临时队列
tmpList.remove(minNode);
// 处理垃圾
itr = null;
tmpNode = null;
return minNode;
}
/**
* 根据权值创建叶子节点并加入临时队列
*
*/
public void makeLeafNode() {
leafArr = new TNode[leafWList.size()];
for (int i = 0; i < leafWList.size(); i++) {
TNode node = new TNode(leafWList.get(i).intValue());
leafArr[i] = node;
tmpList.add(node);
}
}
/**
* 根据权值构造最优的二叉树
*
*/
public void makeBestTree() {
// 根据权值创建叶子节点并加入临时队列
makeLeafNode();
TNode minNode1 = null, minNode2 = null;
TNode node = null;
// 构造最优树
while (tmpList.size() != 1) {
minNode1 = min();
minNode2 = min();
node = new TNode(minNode1.getWeight() + minNode2.getWeight(),
minNode1, minNode2);
tmpList.add(node);
}
root = node;
}
/**
* 先序遍历的递归调用
*
*/
protected void preT(TNode t) {
if (t.isLeaf()) {
System.out.print(t.getWeight() + " ");
return;
} else {
System.out.print(t.getWeight() + " ");
preT(t.lChild);
preT(t.rChild);
}
}
/**
* 先序遍历最优二叉树
*
*/
public void preOrderTraverse() {
preT(root);
}
}