什么是哈夫曼树?
让我们先举一个例子。
判定树:
在很多问题的处理过程中,需要进行大量的条件判断,这些判断结构的设计直接影响着程序的执行效率。例如,编制一个程序,将百分制转换成五个等级输出。大家可能认为这个程序很简单,并且很快就可以用下列形式编写出来:
if(score< 60) System.out.println("不及格"); else if(score< 70) System.out.println("及格"); else if(score< 80) System.out.println("中等"); else if(score< 90) System.out.println("良好"); else System.out.println("优秀!");若考虑上述程序所耗费的时间,就会发现该程序的缺陷。在实际中,学生成绩在五个等级上的分布是不均匀的。当学生百分制成绩的录入量很大时,上述判定过程需要反复调用,此时程序的执行效率将成为一个严重问题。
但在实际应用中,往往各个分数段的分布并不是均匀的。下面就是在一次考试中某门课程的各分数段的分布情况:
下面我们就利用哈夫曼树寻找一棵最佳判定树,即总的比较次数最少的判定树。
第一种构造方式:
第二种构造方式:
这两种方式,显然后者的判定过程的效率要比前者高。在也没有别地判定过程比第二种方式的效率更高。
我们称判定过程最优的二叉树为哈夫曼树,又称最优二叉树
1. 哈夫曼树的基本概念
哈夫曼树( Huffman )又称最优二叉树,是一类带权路径长度最短的树,有着广泛的应用。
在讨论哈夫曼树之前首先需要弄清楚关于路径和路径长度的概念。树中两个结点之间的路径由一个结点到另一结点的分支构成。两结点之间的路径长度是路径上分支的数目。树的路径长度是从根结点到每一个结点的路径长度之和。
设一棵二叉树有 n 个叶子结点,每个叶子结点拥有一个权值W 1 ,W 2 , ...... W n ,从根结点到每个叶子结点的路径长度分别为 L1 , L2......Ln ,那么树的带权路径长度为每个叶子的路径长度与该叶子权值乘积之和。通常记作 WPL。为了直观其见,在图中把带权的叶子结点画成方形,其他非叶子结点仍为圆形。请看图 中的三棵二叉树以及它们的带权路径长。
a) wpl=38 (b) wpl=49 (c) wpl=36
这三棵二叉树叶子结点数相同,它们的权值也相同,但是它们的 wpl 带权路径长各不相同。图 6.21(c)wpl 最小。它就是哈曼树,最优树。哈夫曼树是,在具有同一组权值的叶子结点的不同二叉树中,带权路径长度最短的树。也称最优树。
2. 哈夫曼树的构造
构造哈夫曼树的方法(贪婪方法)
上图是是哈夫曼树构造过程
图(a) 是一个拥有 4 棵小树的森林,图(b) 森林中还有 3 子棵树,图(c) 森林中剩下 2 棵树,图(d) 森林只有一棵树,这棵树就是哈夫曼树。这里或许会提出疑问, n 个叶子构成的哈夫曼树其带权路径长度唯一吗?确实唯一。树形唯一吗?不唯一。因为将森林中两棵权值最小和次小的子棵合并时,哪棵做左子树,哪棵做右子树并不严格限制。图之中的做法是把权值较小的当做左子树 , 权值较大的当做右子树。如果反过来也可以,画出的树形有所不同,但 WPL 值相同。为了便于讨论交流在此提倡权值较小的做左子树 , 权值较大的做右子。
3. 哈夫曼树的应用
(1) 用于最佳判断过程。(上面已提到)
(2) 用于通信编码
在通信及数据传输中多采用二进制编码。为了使电文尽可能的缩短,可以对电文中每个字符出现的次数进行统计。设法让出现次数多的字符的二进制码短些,而让那些很少出现的字符的二进制码长一些。假设有一段电文,其中用到 4 个不同字符A,C,S,T,它们在电文中出现的次数分别为 7 , 2 , 4 , 5 。把 7 , 2 , 4 , 5 当做 4 个叶子的权值构造哈夫曼树
如图所示。在树中令所有左分支取编码为 0 ,令所有右分支取编码为1。将从根结点起到某个叶子结点路径上的各左、右分支的编码顺序排列,就得这个叶子结点所代表的字符的二进制编码,如图所示。
这些编码拼成的电文不会混淆,因为每个字符的编码均不是其他编码的前缀,这种编码称做前缀编码。
关于信息编码是一个复杂的问题,还应考虑其他一些因素。比如前缀编码每个编码的长度不相等,译码时较困难。还有检测、纠错问题都应考虑在内。这里仅对哈夫曼树举了一个应用实例。
4、 哈夫曼树的实现
哈夫曼树节点: public class Node< T> implements Comparable< Node< T>> { private T data; private double weight; private String coding = "";//此节点的哈夫曼编码 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; } public String getCoding(){ return coding;} public void setCoding(String coding){ this.coding = coding;} @Override public String toString(){ return "data:"+this.data+";weight:"+this.weight+";Coding:"+this.coding; } @Override public int compareTo(Node< T> other) { if(other.getWeight() > this.getWeight()){ return 1; } if(other.getWeight() < this.getWeight()){ return -1; } return 0; } } //构造哈夫曼树的类 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); } //递归生成Huffman编码 public static< T> void generateHuffmanCode(Node< T> root){ if (root==null) return; if(root.getLeft()!=null) root.getLeft().setCoding(root.getCoding()+"0"); if(root.getRight()!=null) root.getRight().setCoding(root.getCoding()+"1"); generateHuffmanCode(root.getLeft()); generateHuffmanCode(root.getRight()); } 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 node = queue.poll(); if(node.getLeft() != null){ queue.offer(node.getLeft()); } if(node.getRight() != null){ queue.offer(node.getRight()); } } return list; } } //测试 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>("T",5)); list.add(new Node< String>("S",4)); list.add(new Node< String>("C",2)); Node< String> root = HuffmanTree.createTree(list); HuffmanTree.generateHuffmanCode(root); System.out.println(HuffmanTree.breadth(root)); //测试2 int a[]={5,24,7,17,34,5,13}; String s[]={"A","B","C","D","E","F","G"}; List< Node< String>> list1 = new ArrayList< Node< String>>(); for(int i=0;i< a.length;i++) list1.add(new Node< String>(s[i],a[i])); root = HuffmanTree.createTree(list1); HuffmanTree.generateHuffmanCode(root); List< Node< String>> list2=HuffmanTree.breadth(root); for(Node< String> node: list2) System.out.println(node); } }