![](https://i-blog.csdnimg.cn/blog_migrate/7f953be3599bc0ba6c60dc567b57c361.png)
哈夫曼树根据排序方法不同,也可能不太一样,这样对应的哈夫曼编码也不完全一样,但是wpl 是一样的,都是最小的。
步骤1:根据赫夫曼编码压缩数据的原理,需要创建 "i like like like java do you like a java" 对应的赫夫曼树.
步骤2:
1)生成赫夫曼树对应的赫夫曼编码 , 如下表:
=01 a=100 d=11000 u=11001 e=1110 v=11011 i=101 y=11010 j=0010 k=1111 l=000 o=0011
2)使用赫夫曼编码来生成赫夫曼编码数据 ,即按照上面的赫夫曼编码,将"i like like like java do you like a java" 字符串生成对应的编码数据, 形式如下.
1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100
步骤3:
1)前面我们得到了赫夫曼编码和对应的编码byte[] , 即:[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]
2)现在要求使用赫夫曼编码, 进行解码,又重新得到原来的字符串"i like like likejava do you like a java"
使用赫夫曼编码来解码数据,具体要求是
1)前面我们得到了赫夫曼编码和对应的编码byte[] , 即:[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]
2)现在要求使用赫夫曼编码, 进行解码,又重新得到原来的字符串"i like like likejava do you like a java"
package com.zhen;
import java.util.*;
public class Huffmancode {
public static void main(String[] args) {
String content= "i like like like java do you like a java";
//转为byte数组
byte[] bytes = content.getBytes();
System.out.println("原字符串"+new String(bytes));
byte[] huffmanzip = huffmanzip(bytes);
System.out.println("哈夫曼编码压缩后:"+Arrays.toString(huffmanzip));
byte[] recover=decode(huffman, huffmanzip);
System.out.println("还原后的字符串"+new String(recover));
}
//三步对应的调用方法
private static byte[] huffmanzip(byte[] bytes) {
//第一步,加入集合并生成哈夫曼树
List<Node> list=getNode(bytes);
Node tree=huffman(list);
//第二步,生成哈夫曼编码
getcode(tree);
//第三步,生成压缩后的数组
byte[] zipb = zip(bytes, huffman);
return zipb;
}
//将哈夫曼表存入map<Byte(数值),String(对应的编码值)>例如:32->01,97->100
static Map<Byte, String> huffman=new HashMap<>();
//生成哈夫曼编码表示,需要去拼接路径,定义一个StringBuilder,存储某个叶子节点的路径
static StringBuffer str=new StringBuffer();
/**
* 第一步
* 生成哈夫曼树
* @param bytes 接收字节数组
* @return 返回List的形式
*/
public static List<Node> getNode(byte[] bytes) {
ArrayList<Node> list = new ArrayList<Node>();
//遍历bytes统计每一个字符出现的次数->map[字符,次数]
Map<Byte,Integer> map = new HashMap<Byte,Integer>();
for(byte b:bytes) {
Integer count=map.get(b);
//未出现过
if (count == null) {
map.put(b, 1);
}else {
map.put(b, count+1);
}
}
//将每一个键值对转成Node对象,加入list
for(Map.Entry<Byte, Integer> entry : map.entrySet()) {
list.add(new Node(entry.getKey(), entry.getValue()));
}
return list;
}
public static Node huffman(List<Node> list) {
while(list.size() > 1) {
//排序
Collections.sort(list);
//取出值最小的两颗二叉树
Node left=list.get(0);
Node right = list.get(1);
//构建一颗新的二叉树
Node parent = new Node(null,left.weight+right.weight);
parent.left=left;
parent.right = right;
//删除使用过的节点
list.remove(left);
list.remove(right);
//将新产生的节点加入list
list.add(parent);
}
return list.get(0);
}
/**
* 第二步
* 生成哈夫曼树对应的编码
*/
//为了调用方便重载getcode,实现传入根节点就返回对应的哈夫曼编码
private static Map<Byte, String> getcode(Node root) {
if (root==null) {
return null;
}
//处理root的左子树
getcode(root.left, "0", str);
//处理root的右左子树
getcode(root.right, "1", str);
return huffman;
}
/**
* 将传入的Node节点的所有叶子节点的哈夫曼编码得到,并存入集合
* @param node 传入节点
* @param code 路径:左子节点为0,右为1
* @param stringBuf 用于拼接路径
*/
private static void getcode(Node node,String code,StringBuffer stringBuf) {
StringBuffer str2 = new StringBuffer(stringBuf);
//将code加入str2
str2.append(code);
//如果为null不处理
if (node!=null) {
//判断是否为叶子节点
//不是
if (node.data==null) {
//递归处理
//向左递归
getcode(node.left, "0", str2);
//向右递归
getcode(node.right, "1", str2);
}else {
//是叶子节点表示找到了最后
huffman.put(node.data, str2.toString());
}
}
}
/**
* 第三步:将字符串对应的byte[]数组,通过生成的哈夫曼编码表,返回一个压缩后的byte[]
* @param bytes 原始字符串对应的byte[]
* @param huffmancode 生成的哈夫曼编码表
* @return 返回压缩后的byte[]
*/
private static byte[] zip(byte[] bytes,Map<Byte, String> huffmancode) {
//利用huffmancode将bytes转成哈夫曼编码对应的字符串
StringBuffer strc = new StringBuffer();
//遍历加入
for(byte b:bytes) {
strc.append(huffmancode.get(b));
}
//统计返回byte[]huffmancode的长度
int len;
if (strc.length()%8==0) {
len=strc.length()/8;
}else {
len=strc.length()/8+1;
}
//创建存储压缩后的byte数组
byte[] zipb=new byte[len];
int index=0;
//每8位对应一个byte
for(int i=0;i<strc.length();i+=8) {
String strbyte;
if (i+8>strc.length()) {
//不够8位,有多少取多少
strbyte=strc.substring(i);
}else {
//够8位,每次取出8位
strbyte=strc.substring(i,i+8);
}
//存入byte数组
zipb[index]=(byte)Integer.parseInt(strbyte,2);
index++;
}
return zipb;
}
//将压缩后的byte数组进行解压得到原来的字符串
/**
* 第一步:取出byte[]中的每一个byte转成二进制字符串
* @param flag true表示为正数则需要补高位,false表示不补
* @param b byte[]中取出的每一个byte
* @return b对应的二进制字符串(补码)
*/
private static String bytetos(boolean flag,byte b) {
//因为int类型有转二进制的方法可以直接用,所以首先将byte存为int
int temp=b;
if (flag) {
//与运算补高位,例如:1 0000 0000|0000 0001->1 0000 0001
temp|=256;
}
//返回对应的二进制补码
String str=Integer.toBinaryString(temp);
if (flag) {
//原数是32位的,我们只需要后8位
return str.substring(str.length()-8);
}else {
//不需要补齐则直接返回
return str;
}
}
/**
* 第二步:得到原字符串
* @param hcode 哈夫曼编码表
* @param huffmanb 哈夫曼编码得到的字节数组(17位)
* @return 原来字符串对应的数组
*/
private static byte[] decode(Map<Byte, String> hcode,byte[] huffmanb) {
//先得到对应的二进制字符串
StringBuilder str = new StringBuilder();
//将byte数组转成二进制的字符串
for(int i=0;i<huffmanb.length; i++) {
byte b = huffmanb[i];
//判断是否为最后一个字节
boolean flag = (i==huffmanb.length-1);
str.append(bytetos(!flag, b));
}
//将字符串按照原来的编码表进行解压
//将编码表进行反转key->value,value->key,因为要进行反向查询
Map<String, Byte> map=new HashMap<>();
for(Map.Entry<Byte, String> entry : hcode.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
//存放byte
List<Byte> list = new ArrayList<>();
for (int i = 0; i < str.length();) {
int count=1;
boolean flag=true;
Byte b=null;
while(flag) {
//i不动,count移动去匹配每一个字符
String key = str.substring(i,i+count);
b=map.get(key);
if (b == null) {
count++;
}else {
//匹配到就退出
flag = false;
}
}
list.add(b);
//i直接移动到count
i+=count;
}
//for循环后list中就存入了原先的字符,将list中的数据存入byte并返回
byte[] b=new byte[list.size()];
for (int i = 0; i < b.length; i++) {
b[i]= list.get(i);
}
return b;
}
}
class Node implements Comparable<Node> {
//存放数据(字符)本身,比如"a"->97
Byte data;
//字符出现的次数(权值)
int weight;
Node left;
Node right;
public Node(Byte data, int weight) {
super();
this.data = data;
this.weight = weight;
}
@Override
public int compareTo(Node o) {
// 从小到大排序
return this.weight-o.weight;
}
@Override
public String toString() {
return "Node [data=" + data + ", weight=" + weight + "]";
}
}