关于赫夫曼编码

关于赫夫曼编码这一块,头疼死了,在家有网课还要健身,所以花了两三天的时间才把编码压缩部分搞定,但还不能说自己特别会,略知一二,哈哈哈

直接上代码吧,前提是还要先学会普通树和赫夫曼树的创建

还是需要一个节点类,实现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;
}

压缩好后的结果如图:
在这里插入图片描述

最后是我整个操作下来的一个结果图,差不多已经可以看着结果来理清思路了。
在这里插入图片描述
关于解码操作,我也正在学,还没搞懂,等懂了之后发

有不懂的可以直接私聊,一起交流进步
如果有帮助请点个赞支持支持

请勿转载

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值