java实现-数据结构之二叉树(六):数据压缩-哈夫曼编码

数据结构之二叉树(六):数据压缩-哈夫曼编码

基本介绍:
哈夫曼树是可变字长编码(VLC)的一种,广泛应用于数据文件压缩,其压缩率通常在20%~90%之间。

  • 1.通信领域的信息处理方式1----定长编码
    在这里插入图片描述
    以上一段英文包括空格共有40个字符,它们对应的Ascii码如下:
    在这里插入图片描述
    再将这些Ascii码转化为二进制,便得到了:
    在这里插入图片描述
    总长度为359,也就是说用定长编码发送这句话需要359的长度。

  • 2.通信领域的信息处理方式2----变长编码
    在这里插入图片描述
    仍是这句话,我们计算在这句话中各个字符出现的个数:
    d:1,y:1,u:1,v:2,o:2,l:4,e:4,i:5,a:5,(空格) :9
    我们按照各个字符出现的次数进行编码,出现次数越多的,编码越小,比如空格出现了9次,编码则为0.
    得到如下:
    0=‘(空格)’,1=a,10=i,11=3,100=k,101=l,110=o,111=v,1000=j,1001=u,1010=y,1011=d

按照以上的规则规定的编码,我们再传输这句话时,编码就变成了:
10010110100…(省略不写了,反正大家懂)
这样的编码便有了问题,如1既可以是a,也可以看作是10即i,这样便产生了冲突,需要一定的方式解决
(字符的编码都不能是其他字符编码的前缀,符合此要求的编码叫做前缀编码,即不能匹配到重复的编码。这种问题在哈夫曼编码中得到了解决)

**

3.哈夫曼编码原理剖析:

**
在这里插入图片描述
1)仍是这句话,我们计算在这句话中各个字符出现的个数:
d:1,y:1,u:1,v:2,o:2,l:4,e:4,i:5,a:5,(空格) :9

2)接着,我们按照上面字符出现的次数构建一棵哈夫曼树,次数作为权值
(构建哈夫曼树的方法在第五章中已经阐述,这里不再进行代码表示)

构建后的哈夫曼树如图所示
在这里插入图片描述
3)根据上图构建的哈夫曼树,给各个字符,规定编码,向左的路径为0,向右的路径为1,编码如下
o:1000,u:10010,d:100110,y:100111,i:101,a:110,k:1110,e:1111,j:0000,v:0001,l:001,(空格):01

4)按照上面的哈夫曼编码,
在这里插入图片描述
该字符串对应的编码为(无损压缩):
在这里插入图片描述
此编码满足前缀编码,即字符的编码都不能是其他字符编码的前缀,不会造成匹配的多义性。很好的解决了第二种方式的前缀冲突问题。长度为133。

注意事项:
这个哈夫曼树根据排序方法不同,构建的树可能不是完全一样的,(比如当树中多个节点权值相同的情况),这样对应的哈夫曼编码也是不完全相同的。 但wpl(树的带权路径长度)是一样的,都是最小的。

代码示例
1)创建节点Node(data(数据:存放每个字符对应的Acsii码值),weight(权值:每个字符出现的次数),left,right)
2)得到:"i like like like java do you like a java"对应的byte[]数组
3)编写一个方法,将准备构建哈夫曼树的Node节点放到List,形式(Node[data=97,weitht=5],Node(data=32,weifght=9),…)
4)通过list构建哈夫曼树

总结后的步骤便是
1.通过getBytes()方法将字符串转化为字节数组
2.通过getNodes(bytes)方法将字节数组构建成哈夫曼树的叶子节点
3.通过createHuffmanTree(nodes)方法将叶子节点生成哈夫曼树。
4.通过getCodes(HuffmanRoot)方法利用哈夫曼树创建字符串对应的哈夫曼编码表
5.通过zipHuff(bytes,huffmanCodes)方法利用哈夫曼编码表将字符串转化成二进制形式,再将二进制转换成对应的字节数组。

参考代码

package Tree06;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HuffmanCode {
    
    
	public static void main(String[] args) {
    
    
		String str="i like like like java do you like a java";
		byte[] contentBytes=str.getBytes();
		System.out.println("未压缩前的长度:"+contentBytes.length);
		byte[] huffmanZip = huffmanZip(contentBytes);
		System.out.println("将字符串转换成功后得到的byte数组"+Arrays.toString(huffmanZip));
	}
	/***
	 * 生成哈夫曼编码的总方法
	 * 
	 */
	private static byte[] huffmanZip(byte[] bytes) {
    
    
		//利用传进来的bytes构建哈夫曼树的节点
		List<Node> nodes=getNodes(bytes);
		//利用构建的节点生成哈夫曼树,返回该树的根节点
		Node HuffmanRoot=createHuffmanTree(nodes);
		//利用getCodes方法获取每个字符对应的哈夫曼编码,存入到huffmanCodes编码表中
		getCodes(HuffmanRoot);
		//利用得到的huffman编码表将对应的字符串转换为字节码数组
		byte[] zipHuff = zipHuff(bytes,huffmanCodes);
		return zipHuff;
	}
	
	
	/**
	 * 将字符串对应的byte[]数组,通过生成的哈夫曼编码表,生成哈夫曼编码
	 * @param bytes 原始的字符串对应的byte[](contentBytes)
	 * @param huffmanCodes生成的哈夫曼编码表
	 * @return 返回字符串对应的哈夫曼编码
	 * 此方法的目的是将目的字符串转换成对应的哈夫曼编码后,每八位一个字节装入byte数组里去
	 */
	public static byte[] zipHuff(byte[] bytes,Map<Byte,String>huffmanCodes) {
    
    
		//1.利用huffmanCodes将bytes转换成对应的字符串
		StringBuilder stringBuilder=new StringBuilder();
		for(byte b:bytes) {
    
    
			stringBuilder.append(huffmanCodes.get(b));
		}
		//统计返回byte[] huffmanCodeBytes长度
		int len;
		if(stringBuilder.length()%8==0) {
    
    
			len=stringBuilder.length()/8;
		}else {
    
    
			len=stringBuilder.length()/8+1;
		}
		byte[] huffmanCodeBytes=new byte[len];
		int index=0;
		for(int i=0;i<stringBuilder.length();i+=8) {
    
    
			String strByte;
			if(i+8>stringBuilder.length()) {
    
    
				strByte=stringBuilder.substring(i);
			}else {
    
    
				strByte=stringBuilder.substring(i,i+8);
			}
			//将strByte转成一个byte,放入到huffmanCodeBytes
			huffmanCodeBytes[index]=(byte)Integer.parseInt(strByte, 2);
			index++;
		}
		return huffmanCodeBytes;
	}
	
	
	//生成哈夫曼编码:
	/**
	 * 1.将哈夫曼编码表存放在Map<Byte,String>形式:字符的ascii码->对应的哈夫曼编码
	 * 2.拼接路径,利用StringBuilder存储某个叶子节点的路径
	 * @param root
	 */
	static Map<Byte,String> huffmanCodes=new HashMap<Byte,String>();
	static StringBuilder stringBuilder=new StringBuilder();
	
	//重载getCodes
	private static Map<Byte,String> getCodes(Node root){
    
    
		if(root==null) {
    
    
			return null;
		}
		//处理root的左子树
		getCodes(root.left,"0",stringBuilder);
		//处理右子树
		getCodes(root.right,"1",stringBuilder);
		return huffmanCodes;
	}
	
	//生成传入的node节点的所有叶子节点的哈夫曼编码,并放入集合,code:路径
	private static void getCodes(Node node,String code,StringBuilder stringBuilder) {
    
    
		StringBuilder stringBuilder2=new StringBuilder(stringBuilder);
		stringBuilder2.append(code);
		if(node!=null) {
    
    
			if(node.data==null) {
    
    
				//递归处理
				getCodes(node.left,"0",stringBuilder2);
				getCodes(node.right,"1",stringBuilder2);
			}else {
    
    //说明是一个叶子节点
				//表示找到某个叶子节点
				huffmanCodes.put(node.data, stringBuilder2.toString());
			}
		}
	}
	//前序遍历
		public static void frontShow(Node root) {
    
    
			if(root!=null) {
    
    
				root.frontShow();
			}else {
    
    
				System.out.println("树为空");
			}
		}
	
	private static List<Node> getNodes(byte[] bytes){
    
    
		//创建List
		ArrayList<Node> nodes=new ArrayList<Node>();
		//得到:"i like like like java do you like a java"对应的byte[]数组
		Map<Byte,Integer> counts=new HashMap<>();
		for(byte b:bytes) {
    
    
			Integer count=counts.get(b);
			if(count==null) {
    
    
				counts.put(b,1);
			}else {
    
    
				counts.put(b, count+1);
			}
		}
		
		//把每一个键值对转成一个Node对象,并加入到nodes集合
		for(Map.Entry<Byte,Integer> entry:counts.entrySet()) {
    
    
			nodes.add(new Node(entry.getKey(),entry.getValue()));
		}
		return nodes;
	}
	//通过List创建对应的哈夫曼树
	private static Node createHuffmanTree(List<Node> nodes) {
    
    
		while(nodes.size()>1) {
    
    
			Collections.sort(nodes);
			//取出第一颗最小的二叉树
			Node leftNode=nodes.get(0);
			Node rightNode = nodes.get(1);
			//创建一棵新的二叉树(没有data,只有权值)
			Node parent=new Node(null,leftNode.weight+rightNode.weight);
			parent.left=leftNode;
			parent.right=rightNode;
			
			//将已经处理两颗二叉树移除
			nodes.remove(leftNode);
			nodes.remove(rightNode);
			//加入新的
			nodes.add(parent);
			
		}
		
		return nodes.get(0);
	}
	
	
}

//创建Node
class Node implements Comparable<Node>{
    
    
	Byte data;//存放数据:字符对应的ascii码值
	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) {
    
    
		// TODO Auto-generated method stub
		//升序
		return this.weight-o.weight;
	}
	@Override
	public String toString() {
    
    
		return "Node [data=" + data + ", weight=" + weight + "]";
	}
	
	//前序遍历
	public void frontShow() {
    
    
		System.out.println(this);
		if(this.left!=null) {
    
    
			this.left.frontShow();
		}
		if(this.right!=null) {
    
    
			this.right.frontShow();
		}
	}
}


输出结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_45273552/article/details/109129499