赫夫曼树概述
- 赫夫曼树,也叫哈夫曼树、最优二叉树,他是n个带权叶子结点构成的二叉树中,带权路径长度最小的二叉树。
- 叶结点的带权路径:即从根结点到目标叶子结点的路径长度乘以目标结点的权
- 树的带权路径长度WPL:树中所有的叶子结点的带权路径长度之和
- 权值越大的结点离根结点越近的二叉树才是最优二叉树
赫夫曼树代码的实现
步骤:
- 排
- 取除根节点权值最小的两棵二叉树
- 组成一颗新的二叉树(一个根节点),新二叉树的权值等于前两颗二叉树的权值之和
- 没一个根节点的权值都是前两取出的二叉树的权值之和
public class TestHuffmanTree {
public static void main(String[] args) {
int[] arr = new int[]{3,7,8,29,23,5,11,14};
HufumanTreeNode node = huffmanTree(arr);
//System.out.println(node.rightNode);
middelShow(node);
}
//中序遍历
public static void middelShow(HufumanTreeNode node){
if (node.leftNode != null){
middelShow(node.leftNode);
}
System.out.println(node.value);
if (node.rightNode != null){
middelShow(node.rightNode);
}
}
//创建赫夫曼树
public static HufumanTreeNode huffmanTree(int[] arr){
//创建一个数组存储二叉树
List<HufumanTreeNode> nodes = new ArrayList<>();
//遍历数组使每一个元素创建一个二叉树(只有一个结点)
for (int value:arr) {
nodes.add(new HufumanTreeNode(value));
}
//循环处理
while (nodes.size()>1){
//首先排序
nodes.sort(new Comparator<HufumanTreeNode>() {
@Override
public int compare(HufumanTreeNode o1, HufumanTreeNode o2) {
//降序排列
return o2.value-o1.value;
}
});
//取出权值最小的两棵二叉树
HufumanTreeNode left = nodes.get(nodes.size()-1);
HufumanTreeNode right = nodes.get(nodes.size()-2);
//创建一颗新二叉树
HufumanTreeNode parent = new HufumanTreeNode(left.value+right.value);
//将左右子树加上
parent.leftNode = left;
parent.rightNode = right;
//把取出来的两个二叉树移除
nodes.remove(left);
nodes.remove(right);
//将新的二叉树放回到数组中
nodes.add(parent);
}
//当列表中仅有一个元素时返回当前值
return nodes.get(0);
}
}
public class HufumanTreeNode {
int value;
HufumanTreeNode leftNode;
HufumanTreeNode rightNode;
public HufumanTreeNode(int value) {
this.value = value;
}
public void setLeftNode(HufumanTreeNode leftNode) {
this.leftNode = leftNode;
}
public void setRightNode(HufumanTreeNode rightNode) {
this.rightNode = rightNode;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
}
赫夫曼编码的实现
- 原理分析:在传输信息时,由于计算机只能识别二进制,所以传输的信息最终都要转换成二进制,这就造成了资源消耗大的问题。
- 而如果使用赫夫曼树来解决这一问题,效率就很提高很多。统计每一个字符出现的次数,次数便是权值,然后将这些字符以及他们的权值建成一棵哈夫曼树。说白了就是数据压缩
赫夫曼编码与解码代码实现(数据压缩与解压)
- 统计字符数并排序
- 创建赫夫曼树
- 创建赫夫曼编码表
- 编码
简单地说就是把要传输的消息一个一个存到旧byte数组中,出现的每一个字符统计一下出现次数与该字符作为键值对存入到Map表中,然后建成赫夫曼树,该树的每一个叶子结点就是出现的字符,权值就是该字符出现的次数,该字符代表的二进制就是从根节点到该叶子结点的路径。最后将二进制路径通过补码运算算出十进制,最后再存储到新的byte数组中
//节点类
public class HuffmanTreeCodeNode {
//字符
Byte data;
//权值
int weight;
HuffmanTreeCodeNode leftNode;
HuffmanTreeCodeNode rightNode;
public HuffmanTreeCodeNode(Byte data, int weight) {
this.data = data;
this.weight = weight;
}
@Override
public String toString() {
return "HuffmanTreeCodeNode{" +
"data=" + data +
", weight=" + weight +
'}';
}
}
public class TestHuffmanTreeCode {
public static void main(String[] args) {
String str = "can you can a can as a can canner can a can.";
//创建一个byte数组存储单个字符
byte[] bytes = str.getBytes();
//将该数组进行赫夫曼编码压缩
byte[] b = HuffmanCode(bytes);
//打印压缩前和压缩后的数组长度
System.out.println(bytes.length+"...."+b.length);
//使用赫夫曼编码进行解码
byte[] newBytes = decode(huffCodes,b);
System.out.println(new String(newBytes));
}
/**
* 使用指定的赫夫曼编码表进行编码
* @param huffCodes
* @param bytes
* @return
*/
private static byte[] decode(Map<Byte, String> huffCodes, byte[] bytes) {
//创建一个临时存储的字符串
StringBuilder sb = new StringBuilder();
//把byte数组转化为一个二进制的字符表
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
//判断是否是最后一个
boolean flag = (i == bytes.length-1);
sb.append(byteToBitStr(!flag,b));
}
//把字符串按照指定的赫夫曼编码进行解码
//把赫夫曼编码的键值对进行调换
Map<String,Byte> map = new HashMap<>();
for (Map.Entry<Byte,String> entry:huffCodes.entrySet()){
map.put(entry.getValue(),entry.getKey());
}
//创建一个集合,用于存储byte
List<Byte> list = new ArrayList<>();
//处理字符串,首先取字符串的前一位二进制字符到map中查询,如结果为空,则继续取前两位二进制字符,还是空继续取前三位,直到不为空
for (int i = 0; i < sb.length();) {
//定义一个自增长变量,决定取的二进制字符前几位
int count = 1;
//循环条件
boolean flag = true;
//临时存储字符的byte
Byte b = null;
while (flag){
//首先取前一个字符,然后取前两个,循环下去
String key = sb.substring(i,i+count);
//在map中获取的结果
b = map.get(key);
if (b == null){
//如果结果为空,则继续循环取
count++;
}else {
//如果结果不为空,则不再循环
flag = false;
}
}
//把结果都放入list集合
list.add(b);
//再从后边继续遍历,直到循环结束
i+=count;
}
//把集合转化为数组
byte[] b = new byte[list.size()];
for (int i = 0; i < b.length; i++) {
b[i] = list.get(i);
}
return b;
}
private static String byteToBitStr(boolean flag, byte b) {
int temp = b;
if (flag){
temp |= 256;
}
String str = Integer.toBinaryString(temp);
if (flag){
return str.substring(str.length()-8);
}else {
return str;
}
}
/**
* 进行赫夫曼编码压缩的方法
* @param bytes
* @return
*/
public static byte[] HuffmanCode(byte[] bytes){
//先统计每一个byte出现的次数,并放入一个集合中
List<HuffmanTreeCodeNode> nodes = getNodes(bytes);
//创建一棵赫夫曼树
HuffmanTreeCodeNode tree = creatHuffmanTree(nodes);
System.out.println(tree);
//创建一个赫夫曼编码表
Map<Byte,String> huffCodes = getCodes(tree);
System.out.println(huffCodes);
//编码
byte[] b = zip(bytes,huffCodes);
return b;
}
private static byte[] zip(byte[] bytes, Map<Byte, String> huffCodes) {
//创建一个临时存储的字符串
StringBuilder sb = new StringBuilder();
//把需要压缩的byte数组处理成一个二进制的字符串
for (byte b:bytes) {
sb.append(huffCodes.get(b));
}
System.out.println(sb);
//定义长度
int len;
//如果字符串长度刚好是8的倍数,数组长度就等于sb.length()/8
if (sb.length()%8==0){
len = sb.length()/8;
}else {
//如果字符串长度不是8的倍数,数组长度就等于sb.length()/8+1
len = sb.length()/8+1;
}
//用于存储压缩后的byte
byte[] by = new byte[len];
//记录by的位置
int index = 0;
//遍历字符串
for (int i = 0; i <sb.length() ; i+=8) {
String str;
//如果到了最后不足8位
if (i+8>sb.length()){
//将剩下的放到最后一个数组
str = sb.substring(i);
}else {
//其余的每8个分成一组
str = sb.substring(i,i+8);
}
//将二进制转化为十进制补码
byte byt = (byte)Integer.parseInt(str,2);
by[index] = byt;
index++;
}
return by;
}
//用于临时存储路径
static StringBuilder sb = new StringBuilder();
//用于存储赫夫曼编码
static Map<Byte,String> huffCodes = new HashMap<>();
/**
* 根据赫夫曼树获取赫夫曼编码
* @param tree
* @return
*/
private static Map<Byte, String> getCodes(HuffmanTreeCodeNode tree) {
//如果当前二叉树(单结点)等于空,则说明当前节点不是叶子结点
if (tree == null){
return null;
}
//然后继续递归其左右结点
getCodes(tree.leftNode,"0",sb);
getCodes(tree.rightNode,"1",sb);
return huffCodes;
}
//递归方法
private static void getCodes(HuffmanTreeCodeNode node, String code, StringBuilder sb) {
//创建一个新的存储字符串
StringBuilder sb2 = new StringBuilder(sb);
//将经过的路径连起来
sb2.append(code);
//如果当前结点data为空,则证明不是叶子结点
if (node.data == null){
//递归判断其左右结点,知道找到其叶子结点
getCodes(node.leftNode,"0",sb2);
getCodes(node.rightNode,"1",sb2);
}else {
//找到叶子结点后,将其data值及路径放入map中
huffCodes.put(node.data,sb2.toString());
}
}
public static HuffmanTreeCodeNode creatHuffmanTree(List<HuffmanTreeCodeNode> node){
//排序
while (node.size() > 1){
node.sort(new Comparator<HuffmanTreeCodeNode>() {
@Override
public int compare(HuffmanTreeCodeNode o1, HuffmanTreeCodeNode o2) {
return o2.weight-o1.weight;
}
});
//取出权值两个最低的二叉树
HuffmanTreeCodeNode left = node.get(node.size()-1);
HuffmanTreeCodeNode right = node.get(node.size()-2);
//合成一个新二叉树
HuffmanTreeCodeNode parent = new HuffmanTreeCodeNode(null,left.weight+right.weight);
//将新二叉树加上左右子树
parent.leftNode = left;
parent.rightNode = right;
//删除获取到的两个子树
node.remove(left);
node.remove(right);
//将新树添加到列表中
node.add(parent);
}
return node.get(0);
}
/**
* 把byte数组转换为node集合
* @param bytes
* @return
*/
private static List<HuffmanTreeCodeNode> getNodes(byte[] bytes) {
//创建一个列表用于存储二叉树
List<HuffmanTreeCodeNode> nodes = new ArrayList<>();
//存储每一个byte出现了多少次
Map<Byte,Integer> counts = new HashMap<>();
//统计每一个byte出现的次数
for (byte b:bytes){
Integer count = counts.get(b);
if (count == null){
counts.put(b,1);
}else {
counts.put(b,count+1);
}
}
//把每一个键值对转换为一个对象
for (Map.Entry<Byte,Integer> entry:
counts.entrySet()) {
nodes.add(new HuffmanTreeCodeNode(entry.getKey(),entry.getValue()));
}
return nodes;
}
}