哈夫曼树
哈夫曼树的几个重要概念
1)路径和路径长度:在一棵树中,从一个结点可以到达孩子结点或孙子结点之间的通路,叫做路径,通路中分支的数目成为路径的长度。若规定根结点的层数为1,则从根结点到第n层的路径长度为 n - 1。
2)结点的权及带权路径长度:若赋给树种结点一个含有某种意义的数值,则这个数值称为结点的权。结点的带权路径长度为:从根结点到该结点的路径长度与权的乘积。
3)树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL(weighted path length),权值越大的结点离根结点越近的二叉树才是最优二叉树。
4)WPL最小的就是哈夫曼树
哈夫曼树的构造
步骤:
1)从小到大排序,将每一个结点即数据看成是一个最小的二叉树;
2)取出权值最小的两个二叉树;
3)组成一个新的二叉树,该树的权值为取出的两个二叉树权值之和;
4)将新的二叉树的权值放入数组,然后重复 1 2 3 4。直到所有的数据都被处理
相应代码:
public class HuffmanTree {
public TreeNode creatHuffman(int[] nums){
ArrayList<TreeNode> arr = new ArrayList<>();
for(int i : nums){
arr.add(new TreeNode(i));
}
while(arr.size() > 1){
//1.让结点按照权值从小到大排序
arr.sort(new Comparator<TreeNode>() {
@Override
public int compare(TreeNode o1, TreeNode o2) {
return o1.val - o2.val;
}
});
//2.取出权值最小的两个结点
TreeNode left = arr.get(0);
TreeNode right = arr.get(1);
arr.remove(1);//取出的元素记得删除
arr.remove(0);
//3.创建一个新的结点,该结点权值为取出的权值之和,并把取出的两个结点作为此结点的左右结点
TreeNode parent = new TreeNode(left.val + right.val);
parent.left = left;
parent.right = right;
//4.将新结点加入数组,重复1 2 3 4
arr.add(parent);
}
return arr.get(0);
}
static class TreeNode{
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val) {
this.val = val;
}
@Override
public String toString() {
return "TreeNode{" +
"val=" + val +
'}';
}
}
}
哈夫曼编码
基本介绍
哈夫曼编码是一种编码方式,是一种程序算法。
哈夫曼编码是哈夫曼树再电信通讯中的经典应用之一。
哈夫曼编码广泛应用与压缩之中,压缩率通常在20%~90%之间。
哈夫曼编码是可变字长编码(VLC)的一种。
代码实现获取哈夫曼编码
上面我们已经说过如何建立一个哈夫曼树,这里以字符串的压缩为例,首先创建一个方法获取结点的数组以便于创建哈夫曼树。结点的组成为一个Byte和一个int型的weight代表权重即不同字符出现的次数,要统计次数我们用到了map。
static class Node{
Node left;
Node right;
int weight;
Byte data;
public Node(Byte data,int weight) {
super();
this.weight = weight;
this.data = data;
}
@Override
public String toString() {
return "Node [data=" + data + ", weight=" + weight + "]";
}
}
//统计数据中每个字符出现的次数,将次数作为它们各自的权值
public ArrayList<Node> getNodes(byte[] bytes){
//ArrayList保存每一个结点,用来创建哈夫曼树
ArrayList<Node> arr = new ArrayList<>();
//map用来统计每一个字符出现的次数
HashMap<Byte,Integer> map = new HashMap<>();
for(Byte b : bytes){
if(map.containsKey(b)){
map.put(b,map.get(b) + 1);
}else{
map.put(b, 1);
}
}
//统计完以后加入ArrayList
for(Map.Entry<Byte,Integer> entry: map.entrySet()){
arr.add(new Node(entry.getKey(), entry.getValue()));
}
return arr;
}
获取到List之后就创建哈夫曼树。
//返回一个哈夫曼树的根结点
public Node creatHuffmanTree(ArrayList<Node> arr){
while(arr.size() > 1){
//1.讲结点从小到达排序
arr.sort(new Comparator<Node>(){
@Override
public int compare(Node o1, Node o2) {
// TODO Auto-generated method stub
return o1.weight - o2.weight;
}
});
//2.取出权值最小的两个结点,记得删除
Node left = arr.get(0);
Node right = arr.get(1);
arr.remove(1);
arr.remove(0);
//3.创建新结点
Node parent = new Node(null, left.weight + right.weight);
parent.left = left;
parent.right = right;
arr.add(parent);
//4.重复直到全部数据处理完成
}
return arr.get(0);
}
最后一步就是返回每个字符代表的哈夫曼编码,这样成对出现的数据使用一个map型静态变量存放。
private static HashMap<Byte,String> hashMap = new HashMap<>();
/**
* 获取不同字符对应的哈夫曼编码,并放入map中
* @param root 根结点
* @param type 判断是左结点还是右结点
*/
public void getCodes(Node root,String type,StringBuilder s){
if(root == null) return;
StringBuilder s1 = new StringBuilder(s);
s1.append(type);
if(root.data == null){
getCodes(root.left,"0",s1);
getCodes(root.right,"1",s1);
}else {//如果不为空,则表明是叶子节点,加入map中
hashMap.put(root.data,s1.toString());
}
}
public HashMap<Byte,String> getCodes(Node root){
getCodes(root,"",new StringBuilder());
return hashMap;
}
最后放完整的实现类
public class HuffmanCode {
public static void main(String[] args) {
String s = "hello,world";
byte[] bytes = s.getBytes();
HuffmanCode h = new HuffmanCode();
ArrayList<Node> arr = h.getNodes(bytes);
Node root = h.creatHuffmanTree(arr);
System.out.println();
System.out.println(h.getCodes(root));
}
//统计数据中每个字符出现的次数,将次数作为它们各自的权值
public ArrayList<Node> getNodes(byte[] bytes){
//ArrayList保存每一个结点,用来创建哈夫曼树
ArrayList<Node> arr = new ArrayList<>();
//map用来统计每一个字符出现的次数
HashMap<Byte,Integer> map = new HashMap<>();
for(Byte b : bytes){
if(map.containsKey(b)){
map.put(b,map.get(b) + 1);
}else{
map.put(b, 1);
}
}
//统计完以后加入ArrayList
for(Map.Entry<Byte,Integer> entry: map.entrySet()){
arr.add(new Node(entry.getKey(), entry.getValue()));
}
return arr;
}
//返回一个哈夫曼树的根结点
public Node creatHuffmanTree(ArrayList<Node> arr){
while(arr.size() > 1){
//1.讲结点从小到达排序
arr.sort(new Comparator<Node>(){
@Override
public int compare(Node o1, Node o2) {
// TODO Auto-generated method stub
return o1.weight - o2.weight;
}
});
//2.取出权值最小的两个结点,记得删除
Node left = arr.get(0);
Node right = arr.get(1);
arr.remove(1);
arr.remove(0);
//3.创建新结点
Node parent = new Node(null, left.weight + right.weight);
parent.left = left;
parent.right = right;
arr.add(parent);
//4.重复直到全部数据处理完成
}
return arr.get(0);
}
private static HashMap<Byte,String> hashMap = new HashMap<>();
/**
* 获取不同字符对应的哈夫曼编码,并放入map中
* @param root 根结点
* @param type 判断是左结点还是右结点
*/
public void getCodes(Node root,String type,StringBuilder s){
if(root == null) return;
StringBuilder s1 = new StringBuilder(s);
s1.append(type);
if(root.data == null){
getCodes(root.left,"0",s1);
getCodes(root.right,"1",s1);
}else {//如果不为空,则表明是叶子节点,加入map中
hashMap.put(root.data,s1.toString());
}
}
public HashMap<Byte,String> getCodes(Node root){
getCodes(root,"",new StringBuilder());
return hashMap;
}
static class Node{
Node left;
Node right;
int weight;
Byte data;
public Node(Byte data,int weight) {
super();
this.weight = weight;
this.data = data;
}
@Override
public String toString() {
return "Node [data=" + data + ", weight=" + weight + "]";
}
}
}