哈夫曼树又称为最优二叉树。
1、路径和路径长度
在一棵树中,从一个节点往下可以达到的孩子或者子孙节点之间的通路称为路径。通路中分支的数目称为路径长度。若规定根节点的层数为1,则从根节点
到第L层节点的路径长度为L-1.
2、节点的权和带权路径长度
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。
3、树的带权路径长度
树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。
4、哈夫曼树的构造:
假设有n个权值,则构造出的哈夫曼树有N个叶子节点。n个权值分别设为w1、w2……wn,则哈夫曼树的构造规则为:
1、将w1、w2……wn看成是有n棵树的森林(每棵树仅有一个节点);
2、在森林中选出两个根节点权值最小的树合并,作为一棵新树的左、右子树,且新树的根节点权值为其左、右子树根节点权值之和;
3、从森林中删除选取的两棵树,并将新树加入森林;
4、重复2、3步,直到森林中只剩一棵树为止,该树即为所求的的哈夫曼树。
5、基本性质:
具有n个叶子节点的哈夫曼树,一共需要2n-1个叶子节点。
6、java实现构造哈夫曼树以及对哈夫曼树的广度优先遍历:
Node.java
/**
* 哈夫曼树的结点类
* @author lmb
*
*/
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) {
super();
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;
}
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;
}
}
HuffmanTree.java
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import edu.emory.mathcs.backport.java.util.Collections;
public class HuffmanTree<T> {
public static void main(String[] args){
List<Node<String>> list = new ArrayList<Node<String>>();
list.add(new Node<String>("a",9));
list.add(new Node<String>("b",5));
list.add(new Node<String>("c",3));
list.add(new Node<String>("d",7));
list.add(new Node<String>("e",8));
list.add(new Node<String>("f",6));
//构造一棵哈夫曼树
Node<String> root = createTree(list);
//广度优先遍历刚刚构造的哈夫曼树
List<Node<String>> nodes = breadth(root);
for (int i = 0; i < nodes.size(); i++) {
System.out.println(nodes.get(i));
}
}
//创建哈夫曼树
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中(使用但不移出)
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;
}
}
运行结果:
data : null; weight : 38.0
data : null; weight : 16.0
data : null; weight : 22.0
data : null; weight : 8.0
data : e; weight : 8.0
data : a; weight : 9.0
data : null; weight : 13.0
data : c; weight : 3.0
data : b; weight : 5.0
data : f; weight : 6.0
附录:java.util.Queue用法
队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
在队列这种数据结构中,最先插入的元素将是最先被删除的元素;反之最后插入的元素将是最后被删除的元素,因此队列又称为“先进先出”(FIFO—first in first out)的线性表。
在java5中新增加了java.util.Queue接口,用以支持队列的常见操作。该接口扩展了java.util.Collection接口。
Queue使用时要尽量避免Collection的add()和remove()方法,而是要使用offer()来加入元素,使用poll()来获取并移出元素。它们的优点是通过返回a.u值可以判断成功与否,add()和remove()方法在失败的时候会抛出异常。 如果要使用前端而不移出该元素,使用element()或者peek()方法。
值得注意的是LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。