此篇博客讲最优二叉树也叫哈夫曼树的原理,以及构建步骤,还有哈夫曼编码原理。建议有二叉树基础朋友学习交流。对二叉树基础可以看我的另外一篇博客二叉树的构建以及遍历
哈夫曼树引出:
我们平时期末成绩判断,不仅需要给出分数的具体值,还需要给出分数所处的段位,例如优秀、良好、及格、不及格;我们计算机怎样进行判断呢?
上面的判断从最终结果来看并没有什么问题,但是我要要考虑效率,当我们每次拿到成绩时候都要从是否及格开始一连串的判断,这样一定会使判断效率低下。抽象成判断树如下:
一张优秀的试卷我们需要让大量同学集中在中等或良好的范围,优秀和不及格同学应该尽量的少。所以这棵树我们还要考虑各层级学生占比例问题,所以结点间联系要有权重。
如果按照上面代码那样判断,那么百分之80的人都需要经过3次以上的判断。这显然不合理,所以我们可以考虑下面这样组织判断:
从上图我们能看出大部分人不用经过多次判断,但是我们是怎样设计这样的树呢?这就是我们赫夫曼树需要做的,赫夫曼树也称为最优二叉树。
哈夫曼树原理及实现
定义与原理
-
从树中一个结点到另一个结点间的分支构成两个结点间的路径,路径上的分支数目叫做路径长度
例如上图二叉树a中从根结点到结点D路径长度为4,二叉树b为2。 -
树的路径长度就是根节点到树中每一个结点路径长度之和;
例如上图二叉树a的路径长度之和为1+1+2+2+3+3+4+4=20。 -
考虑带权的结点,树的带权路径长度为(路劲X权重),只计算带权的结点,不带权相当于X0=0;
例如二叉树a带权路径长度WPL为
我们赫夫曼树就是求WPL最小的树,既为赫夫曼树。
哈夫曼树构造
- 构造哈夫曼树算法步骤
- 以权值分别为W1、W2、W3…的n个结点,构成n棵二叉树T1、T2、T3…Tn(意思就是刚开始默认每棵二叉树只有一个结点,所以就是n个结点n棵二叉树),组成森林F={T1、T2、T3…Tn}。
每颗二叉树Ti仅有一个权值为Wi的根结点。 - 在F中选取两颗根结点权值最小的树,作为左右子树构造一棵新二叉树,并且置新二叉树根节点权值为左右子树根结点权值之和。(根节点权值=左右子树根结点权值之和,叶子结点权值=Wi)
- 从F中删除这两棵二叉树,并将新二叉树加入到F中,让它可以和别的剩余二叉树一起比较。
- 不断重复2.3两步,直到F中只有一棵二叉树为止。
- F中最后剩下的二叉树就是哈夫曼树。
- 举例演示
假如一篇文章中A出现频率为27%,27就是结点A的权重,其它的A27,B8,C15,D15,E30,F5;他们加起来就是100%。
我们用上面构造哈夫曼树方法来把他们构造成哈夫曼树如下:
哈夫曼树Java代码实现
哈夫曼树的节点类,为了方便使用集合类的排序功能,实现了Comparable接口(可以不是实现该接口,此时需要实现排序功能)
package my.huffmanTree;
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){
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;
}
@Override
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;
}
}
然后:实现哈夫曼树的主题类,其中包括两个静态的泛型方法,为创建哈夫曼树和广度优先遍历哈夫曼树
package my.huffmanTree;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
public class HuffmanTree<T> {
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.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;
}
}
最后:编写一共测试端
package my.huffmanTree;
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Node<String>> list = new ArrayList<Node<String>>();
list.add(new Node<String>("a",7));
list.add(new Node<String>("b",5));
list.add(new Node<String>("c",4));
list.add(new Node<String>("d",2));
Node<String> root = HuffmanTree.createTree(list);
System.out.println(HuffmanTree.breadth(root));
// System.out.println(list);
}
}