在这里主要回顾一下:哈夫曼树、带权路径的计算、哈夫曼树的构造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)); //广度优先遍历输出
}
}
测试结果:
来源:https://www.cnblogs.com/LZYY/p/3450658.html




