关于赫夫曼编码这一块,头疼死了,在家有网课还要健身,所以花了两三天的时间才把编码压缩部分搞定,但还不能说自己特别会,略知一二,哈哈哈
直接上代码吧,前提是还要先学会普通树和赫夫曼树的创建
还是需要一个节点类,实现Comparable接口,指定比较权重weight,自动生成getter和setter方法,toString方法以及一个有参和一个无参构造器。
class Node implements Comparable<Node> {
private Byte bytes;//表示字符串每个字符的字节
private int weight;//表示权重,也就是每个字符出现的次数,不用我多解释了吧
private Node leftNode;//左子节点
private Node rightNode;//右子节点
public Node(Byte bytes, int weight) {
this.bytes = bytes;
this.weight = weight;
}
public Node() {
}
public Byte getBytes() {
return bytes;
}
public void setBytes(Byte bytes) {
this.bytes = bytes;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public Node getLeftNode() {
return leftNode;
}
public void setLeftNode(Node leftNode) {
this.leftNode = leftNode;
}
public Node getRightNode() {
return rightNode;
}
public void setRightNode(Node rightNode) {
this.rightNode = rightNode;
}
@Override
public String toString() {
return "Node{" +
"bytes=" + bytes +
", weight=" + weight +
'}';
}
@Override
public int compareTo(Node o) {
return this.weight - o.weight;
}
public void preOrder() {
System.out.println(this);
if (this.leftNode != null) {
this.leftNode.preOrder();
}
if (this.rightNode != null) {
this.rightNode.preOrder();
}
}
}
下一步直接在测试类中写,接下来我一个方法一个方法的写,容易解释
public static List<Node> getNodes(byte[] bytes) {
List<Node> list = new ArrayList<>();//定义一个集合,用来存放map形式的数据
Map<Byte, Integer> maps = new HashMap<>();//定义一个map按照 字节=权重 的形式
for (byte b : bytes) {
Integer count = maps.get(b);//每次遍历都检查map中是否存在当前字节
if (count == null) {
maps.put(b, 1);//不存在,则按照value为 1 put进去
} else {
maps.put(b, count + 1);//否则value再往后加 1
}
}
for (Map.Entry<Byte, Integer> entry : maps.entrySet()) {//用entry遍历map
list.add(new Node(entry.getKey(), entry.getValue()));//遍历一次加入集合一次
}
return list;
}
下面是将这个集合转换为赫夫曼树的方法,因为构建赫夫曼树,就需要一个顺序数组,并且每次取出前两个数据比较,所以我们用的集合存储。
因为Collection有sort方法,比较方便
public static Node huffmanTree(List<Node> list){
while(list.size() > 1){//只要集合中的元素大于一,就说明还没比较完毕
Collection.sort(list);//先排序集合
//取出最小的两个节点
Node leftNode = list.get(0);
Node rightNode = list.get(1);
//设置父节点,父节点的权值是左右子节点权值相加
Node parent = new Node(null,leftNode.getWeight() + rightNode.getWeight());
//挂载子节点
parent.setLeftNode(leftNode);
parent.setRightNode(rightNode);
//上述步骤完成后记得要将集合中的这两个元素移除
list.remove(leftNode);
list.remove(rightNode);
//再将父节点加入到集合后
list.add(parent);
}
return list.get(0);//因为循环到最后集合中只会剩下一个节点,也就是一个根节点,所以获取集合的第一个元素即可
}
因为赫夫曼树是带权值路径最短的树,因此我们在压缩数据的时候才选择用赫夫曼树来存储。
下面的方法就是求出带权路径,是这个效果
这里我们规定,向左的路径为0,向右的路径为1
/*
说明一下参数
node:传入一个节点
path:每一次递归的路径值
builder:缓存字符串,用来把这些路径值连接成一串,也就是一串二级制编码
*/
//用来表示编码后和赫夫曼树
static Map<Byte, String> huffmanCodes = new HashMap<>();
//拼接字符串,用来存储赫夫曼树的路径
static StringBuilder builder = new StringBuilder();
public static void getPath(Node node,String path,StringBuilder builder){
StringBuilder stringBuilder1 = new StringBuilder(builder);
stringBuilder1.append(path);//将传入的路径值添加到缓存数组中
if(node != null){
if(node.getBytes == null){//如果节点的bytes值为空,说明是非叶子节点,因为只有叶子节点上才有bytes
//向左递归
getPath(node.getLeftNode,"0",stringBuilder1);
//向右递归
getPath(node.getRightNode,"1",stringBuilder1);
}else{
huffmanCodes.put(node.getBytes,stringBuilder1.toString());
}
}
}
//这里写一个重载的getPath方法,在测试的时候更简单,你也可以不写
public static Map<Byte,String> getPath(Node root){//只需要传一个根节点即可
if(root == null){
return null;
}
getPath(node.getLeftNode,"0",builder);
getPath(node.getRightNode,"1",builder);
return huffmanCodes;
}
剩下的一个方法就是编码压缩了
思路:先将上述得到的每一个字符的编码连接起来,形成一个二进制字符串,这个二进制字符串实际上是补码,因此在将8位二进制转换成一个十进制时需要考虑补码->反码->原码的问题
public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
StringBuilder builder = new StringBuilder();
for (byte b : bytes) {
builder.append(huffmanCodes.get(b));//整合Map中的value值
}
System.out.println(builder);
int length;//用来设置数组的大小
if (builder.length() % 8 == 0) {//每八位压缩一次
length = builder.length() / 8;
} else {
length = builder.length() / 8 + 1;//不足8位的补齐即可
}
byte[] huffmanZipCodes = new byte[length];
int index = 0;//数组的索引
for (int i = 0; i < builder.length(); i += 8) {
//首先要做的是拿出8位
String str;
if (i + 8 > builder.length()) {//字符串的最后不够8位
str = builder.substring(i);
} else {
str = builder.substring(i, i + 8);
}
huffmanZipCodes[index] = (byte) Integer.parseInt(str, 2);//
index++;
}
return huffmanZipCodes;
}
压缩好后的结果如图:
最后是我整个操作下来的一个结果图,差不多已经可以看着结果来理清思路了。
关于解码操作,我也正在学,还没搞懂,等懂了之后发
有不懂的可以直接私聊,一起交流进步
如果有帮助请点个赞支持支持
请勿转载