赫夫曼树
基本介绍:
(1)给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,城这样的二叉树为最优二叉树,也称为哈夫曼树(HuffmanTree)
(2)赫夫曼树是带权路径最短的树,权值较大的结点离根较近
赫夫曼树几个重要的概念:
(1)路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称之为路径,通路中分支的数目称之为路径长度,若规定根结点的层数为1,则从根接地那啊到底L层结点的路径长度为L-1。
(2)结点的权以及带权路径长度:若将树中的结点赋给一个有着某种含义的数值,则这个数值称之为该节点的权。结点的带权路径长度为:从根结点到该节点之间的路径长度与该结点的权的乘积
(3)树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL(weight path length),权值越大的结点离根结点越近的二叉树才是最优二叉树
(4)WPL最小的就是赫夫曼树(如下图中间的就是赫夫曼树)
将一个数列构建赫夫曼树的步骤
(1)从小到大进行排序,每个数据看成是一个结点,每个结点可以看成是一棵最简单的二叉树
(2)取出根结点权值最小的两棵二叉树
(3)组成一棵新的二叉树,该新的二叉树根结点的权值是前面两棵二叉树根结点权值的和
(4)在将这棵新的二叉树,以根结点的权值大小再次排序,不断重复1-2-3-4的步骤,知道数列中,所有的数据都被处理,就得到了一棵赫夫曼树
代码实现:
public class HuffManTreeDemo {
public static void main(String[] args) {
int[] arr = {13,7,8,3,29,6,1};
HuffNode temp = createHuffManTree(arr);
perOrder(temp);
}
//前序遍历赫夫曼树
public static void perOrder(HuffNode root) {
if(root == null) {
System.out.println("赫夫曼树是空的");
return ;
}
root.preOrder();
}
//创建赫夫曼树
public static HuffNode createHuffManTree(int[] arr) {
//将数组放到HuffNode结点中
ArrayList<HuffNode> list = new ArrayList<>();
for(int i = 0;i < arr.length-1;i++) {
list.add(new HuffNode(arr[i]));
}
//对集合进行循环取值,构造赫夫曼树
while(list.size() > 1) {
//重新对集合里面的数据进行排序
Collections.sort(list);
//取出集合里面最小的两个值
HuffNode node1 = list.get(0);
HuffNode node2 = list.get(1);
//将这两个结点合并成一个二叉树
//创建父亲结点
HuffNode parent = new HuffNode(node1.value+node2.value);
parent.left = node1;
parent.right = node2;
//将刚刚取出的两个结点从集合里面删除
list.remove(node1);
list.remove(node2);
//将创建好的二叉树的根结点返回到集合里面
list.add(parent);
}
return list.get(0);
}
}
//为了能让Node实现Collection集合排序,让HuffNode实现Comparable接口
class HuffNode implements Comparable<HuffNode> {
int value;
HuffNode left;
HuffNode right;
public HuffNode(int value) {
this.value = value;
}
//前序遍历
public void preOrder() {
System.out.println(this);
if(this.left != null)
this.left.preOrder();
if(this.right != null)
this.right.preOrder();
}
@Override
public String toString() {
return "Node[value="+ value+"]";
}
@Override
//重写排序的关键函数
public int compareTo(HuffNode o) {
//从小到大排序
return this.value - o.value;
}
}
赫夫曼编码
(1)赫夫曼编码也翻译为哈夫曼编码(Huffman Coding),是一种编码方式,属于一种程序设计算法
(2)赫夫曼编码是赫夫曼树在电讯通信中的经典的应用之一 (3)赫夫曼编码广泛用于数据文件压缩,其压缩率通常在20%~90%之间
(4)赫夫曼码是可变字长编码(VLC)的一种,被称之为最佳编码
哈夫曼编码就是利用赫夫曼树,将字符串中每个字符出现的次数和字符装装到一个结点,并且以每个字符出现的次数来将他们构造成一棵哈夫曼树,在通过根节点到指定结点的关键路径来确定每个字符的编码是多少
将字符串“i like like like java do you like a java”,用哈夫曼编码的方式给他进行压缩
代码实现:
public class HuffmanCodeDemo {
public static void main(String[] args) {
String s = "i like like like java do you like a java";
byte[] sByte = s.getBytes();
HuffCodeNode temp = createHuffManTree(getHuffCode(sByte));
temp.preOrder();
HuffmanCode(temp,"",stringBuilder);
}
//创建一个map用于存放哈夫曼编码表
static Map<Byte,String> huffmanCode = new HashMap<Byte,String>();
//创建一个StringBuilder用于拼接哈夫曼 编码
static StringBuilder stringBuilder = new StringBuilder();
//创建哈夫曼编码表,用于查找每个字符用什么来代替
public static void HuffmanCode(HuffCodeNode node,String code,StringBuilder stringBuilder) {
StringBuilder stringBuilder1 = new StringBuilder(stringBuilder);
//先将现在的位置记下来
stringBuilder1.append(code);
//确定这个点是不是需要放到表里的点,data为0的点就说明它不是我们之前存储的字符,所以就不存它
if(node.data == 0) {
//往这个点的左边去找叶子结点,因为只有叶子结点才是我们之前放在哈夫曼树里的字符
HuffmanCode(node.left,"0",stringBuilder1);
//往这个点的右边去找叶子结点
HuffmanCode(node.right,"1",stringBuilder1);
} else {//如果这个点的data有值,那么它就是我们之前存进去的字符,就要把他存进哈夫曼表里
huffmanCode.put(node.data,stringBuilder1.toString());
}
}
//将字节数组装换成HuffCodeNode集合
public static ArrayList<HuffCodeNode> getHuffCode(byte[] arr) {
ArrayList<HuffCodeNode> list = new ArrayList<>();
//计算每个字节出现的次数,将这个次数作为创建哈夫曼树的权值
Map<Byte,Integer> map = new HashMap<>();
for(int i = 0; i < arr.length;i++) {
Integer number = map.get(i);
if(number == null) {
map.put(arr[i],1);
}else {
map.put(arr[i],number+1);
}
}
//将每个字节以及它出现的次数装进HuffCodeNode结点
for(Map.Entry<Byte,Integer> entry : map.entrySet()) {
HuffCodeNode temp = new HuffCodeNode(entry.getKey(),entry.getValue());
list.add(temp);
}
return list;
}
//前序遍历赫夫曼树
public static void perOrder(HuffCodeNode root) {
if(root == null) {
System.out.println("赫夫曼树是空的");
return ;
}
root.preOrder();
}
//创建赫夫曼树
public static HuffCodeNode createHuffManTree(ArrayList<HuffCodeNode> list) {
//对集合进行循环取值,构造赫夫曼树
while(list.size() > 1) {
//重新对集合里面的数据进行排序
Collections.sort(list);
//取出集合里面最小的两个值
HuffCodeNode node1 = list.get(0);
HuffCodeNode node2 = list.get(1);
//将这两个结点合并成一个二叉树
//创建父亲结点
HuffCodeNode parent = new HuffCodeNode(node1.number+node2.number);
parent.left = node1;
parent.right = node2;
//将刚刚取出的两个结点从集合里面删除
list.remove(node1);
list.remove(node2);
//将创建好的二叉树的根结点返回到集合里面
list.add(parent);
}
return list.get(0);
}
}
//为了能让Node实现Collection集合排序,让HuffNode实现Comparable接口
class HuffCodeNode implements Comparable<HuffCodeNode> {
int number;
byte data;
HuffCodeNode left;
HuffCodeNode right;
public HuffCodeNode(int number) {
this.number = number;
}
public HuffCodeNode(byte data,int number) {
this.number = number;
this.data = data;
}
//前序遍历
public void preOrder() {
System.out.println(this);
if(this.left != null)
this.left.preOrder();
if(this.right != null)
this.right.preOrder();
}
@Override
public String toString() {
return "Node[data="+ data +",value="+ number+"]";
}
@Override
//重写排序的关键函数
public int compareTo(HuffCodeNode o) {
//从小到大排序
return this.number - o.number;
}
}