哈夫曼树与哈弗曼编码
在这里主要回顾一下:哈夫曼树、带权路径的计算、哈夫曼树的构造java实现、以及哈弗曼编码应用
相关定义:
哈夫曼树(Huffman tree):又称最优二叉树,就是给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,则就称为哈夫曼树。
权值:哈夫曼树的权值是自己定义的,他的物理意义表示数据出现的次数、频率。可以用树的每个结点数据域data存放一个特定的数表示它的值。
路径长度:在一棵树中,从一个结点往下可以达到的孩子或子孙结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1,这有点像我们楼层的定义,一楼和二楼的楼层距离是1。
结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权值的乘积。 树中所有叶子节点的带权路径长度之和,WPL= (W1*L1+W2*L2+W3*L3+...+Wn*Ln)。
图解:
哈夫曼树构造过程:
假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:
(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点),按照权值排序;
(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
(3)从森林中删除选取的两棵树,并将新树加入森林;
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。
图解:
注意:哈夫曼树只含有度为0和度为2的点。刚开始学的时候又弄混过,记录下:
(7个字符在电文总出现的概率分别是0.10、0.20、0.05、0.15、0.07、0.13、0.30,由此构造的哈夫曼树,所以的节点要么度为0,要么度为2)
应用:
主要用于通信编码
在通信及数据传输中多采用二进制编码。为了使电文尽可能的缩短,可以对电文中每个字符出现的次数进行统计。设法让出现次数多的字符的二进制码短些,而让那些很少出现的字符的二进制码长一些。假设有一段电文,其中用到 4 个不同字符{A,C,S,T,},它们在电文中出现的次数分别为{ 7 , 2 , 4 , 5} 。把 {7 , 2 , 4 , 5} 当做 4 个叶子的权值构造哈夫曼树,并且在树中令所有左分支取编码为 0 ,令所有右分支取编码为1。将从根结点起到某个叶子结点路径上的各左、右分支的编码顺序排列,就得这个叶子结点所代表的字符的二进制编码,如果不这样的话,每个字符的都按照相同长度的二进制码长,则最后得到的二进制码总长度肯定会比较长,所以也经常用哈夫曼树去压文件。
如图所示:
(图中18改成19)
这些编码拼成的电文不会混淆,因为每个字符的编码均不是其他编码的前缀,这种编码称做前缀编码,好神奇。也很容易理解,但是这种非等长的编码方式给读码带来了困难,这给代码实现带来了困难。
代码:
import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.Scanner; /** * 时间:2013年12月1号 * 目的:实现哈夫曼树 * 介绍: * 树的节点类Node<T> , * 利用列表list对节点排序方法sort(List<Node> list) , * 给定一个list,构造哈夫曼树的方法crateHufManTree(List<Node> list), * 给出根节点,广度优先遍历一棵树的方法deepFirst(Node root) * 一个测试实例 * @author lm */ public class HufManTree{ public static class Node<T> { T date; //参数类型 int weight; //节点权值 Node<T> lChild; Node<T> rChild; public Node(T date,int weight){ this.date = date; this.weight = weight; } public String toString() {return date +" " + weight ;} //比较两节点的大小 public boolean compareTo(Node<T> node) { if(this.weight < node.weight) {return true; } return false; } } //排序,从大到小,用列表,线性存储各个节点,这里是用set方法交换两个元素 public static void sort(List<Node> list) { for(int i=0;i<list.size();i++) for(int j=i+1;j<list.size();j++) { if(list.get(i).compareTo(list.get(j))) { Node node = list.get(i); list.set(i,list.get(j)); list.set(j, node); } } } //有了前面的准备,下面就是创建过程 @SuppressWarnings("unchecked") public static Node crateHufManTree(List<Node> list) { while(list.size()>1) { sort(list); //先排序 Node lChild = list.get(list.size() - 1); //列表里最小的元素 Node rChild = list.get(list.size() - 2); //第二小的元素 Node parent = new Node("父节点 ",lChild.weight + rChild.weight); //父节点 parent.lChild = lChild; //左孩子会比较小 parent.rChild = rChild; list.remove(list.size()-1); //删除俩元素 list.remove(list.size()-1); //减1后在减去1 list.add(parent); } return list.get(0); } //广度优先遍历树,用于输出 public static List<Node> deepFirst(Node root) { List<Node> list = new ArrayList<Node>(); Queue<Node> queue = new ArrayDeque<Node>(); queue.add(root); while(!queue.isEmpty()) { list.add(queue.peek()); //检索,但不删除此队列的头,或者返回null如果这个队列是空的。 //此队列的头部,或null如果这个队列是空的 Node x = queue.poll(); //检索并删除此队列的头,或者返回null如果这个队列是空的 if(x.lChild!=null) {queue.add(x.lChild);} if(x.rChild!=null) {queue.add(x.rChild);} } return list; } public static void main(String[] args) { List<Node> list = new ArrayList<Node>(); //new一个新的列表存储各个节点 Scanner cin = new Scanner(System.in); System.out.println("输入用于构造哈夫曼树的节点总数:"); int n = cin.nextInt(); System.out.println("输入各个节点以及他们的权值:"); for(int i=0;i<n;i++) { String c = cin.next(); int x = cin.nextInt(); Node<String> node = new Node<String>(c,x); list.add(node); } @SuppressWarnings("unchecked") Node<String> root = crateHufManTree(list); //创建一棵哈夫曼树后取得节点 System.out.println(deepFirst(root)); //广度优先遍历输出 } }
测试结果: