哈夫曼编码

来源:互联网 发布:徐州淘宝摄影 编辑:程序博客网 时间:2024/05/17 10:28

哈夫曼树又称最优树(二叉树),是一类带权路径最短的树。构造这种树的算法最早是由哈夫曼(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);
}
}