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

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

注释:本文接上篇《数据结构之二叉树(六):数据压缩-哈夫曼编码》
链接:
《数据结构之二叉树(六):数据压缩-哈夫曼编码》

引言:
在上一篇中我们利用哈夫曼编码成功的将目标字符串:
i like like like java do you like a java
压缩成了byte数组:
[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]
压缩后成功使其长度由40压缩到了17.
生成哈夫曼编码的方法步骤是
1.通过getBytes()方法将字符串转化为字节数组
2.通过getNodes(bytes)方法将字节数组构建成哈夫曼树的叶子节点
3.通过createHuffmanTree(nodes)方法将叶子节点生成哈夫曼树。
4.通过getCodes(HuffmanRoot)方法利用哈夫曼树创建字符串对应的哈夫曼编码表
5.通过zipHuff(bytes,huffmanCodes)方法利用哈夫曼编码表将字符串转化成二进制形式,再将二进制转换成对应的字节数组。

哈夫曼解码的方法步骤是:
1.先编写byteToBitString()方法实现将一个byte转化成一个二进制的字符串(对反码,补码,原码需要有基础的理解)
参考代码方法:

/***
	 * 将一个byte转成一个二进制的字符串。
	 * @param flag 标志是否需要补高位,如果是true,表示补高位,反之不补
	 * @param b 传入的byte
	 * @return 是该byte对应的二进制的字符串
	 */
static String byteToBitString(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;
		}
	}

2.解码
1)首先利用byteToBitString得到huffmanBytes对应的二进制字符串,形式如:1010100010111.
2)根据哈夫曼编码表对二进制字符串进行反向查询(这里用到的算法挺精妙,请细看,小编会将它独立提取出来)
3)将反向查询得到的byte数组转换成字符串,得到原字符串
参考代码方法:

/***
	 * 
	 * @param huffmanCodes 哈夫曼编码表 map
	 * @param huffmanByte 哈夫曼编码得到的字节数组
	 * @return 返回原来的字符串对应的字符数组
	 */
	private static byte[] decode(Map<Byte,String> huffmanCodes,byte[] huffmanByte) {
    
    
		//1.先得到huffmanBytes对应的二进制的字符串,形式1010100010111.。
		StringBuilder stringBuilder=new StringBuilder();
		//将byte数组转换成二进制的字符串
		for(int i=0;i<huffmanByte.length;i++) {
    
    
			//判断是否是最后一个字节(最后一个字节不用补高位
			boolean flag=(i==huffmanByte.length-1);
			stringBuilder.append(byteToBitString(!flag,huffmanByte[i]));
		}
		//把字符串安装指定的哈夫曼编码进行解码
		
		//把哈夫曼编码表进行调换,反向查询a->100,100->a
		Map<String,Byte> map=new HashMap<String,Byte>();
		for(Map.Entry<Byte, String>entry:huffmanCodes.entrySet()) {
    
    
			map.put(entry.getValue(), entry.getKey());
		}
		//创建一个集合,存放byte
		List<Byte> list=new ArrayList<>();
		for(int i=0;i<stringBuilder.length();) {
    
    
			int count=1;
			boolean flag=true;
			Byte b=null;
			
			while(flag) {
    
    
				//1010100010111...
				//递增取出key1
				String key=stringBuilder.substring(i,i+count);//i不动,让count移动,	指定匹配下一个字符
				b=map.get(key);
				if(b==null) {
    
    //说明没有匹配到
					count++;
				}else {
    
    
					flag=false;
				}
			}
			list.add(b);
			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;
		
	}

全部代码:

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));
		System.out.println("压缩后的长度:"+huffmanZip.length);
		
		byte[] sourceBytes= decode(huffmanCodes,huffmanZip);
		System.out.println("原来的字符;"+new String(sourceBytes));
	}
	/***
	 * 
	 * @param huffmanCodes 哈夫曼编码表 map
	 * @param huffmanByte 哈夫曼编码得到的字节数组
	 * @return 返回原来的字符串对应的字符数组
	 */
	private static byte[] decode(Map<Byte,String> huffmanCodes,byte[] huffmanByte) {
    
    
		//1.先得到huffmanBytes对应的二进制的字符串,形式1010100010111.。
		StringBuilder stringBuilder=new StringBuilder();
		//将byte数组转换成二进制的字符串
		for(int i=0;i<huffmanByte.length;i++) {
    
    
			//判断是否是最后一个字节(最后一个字节不用补高位
			boolean flag=(i==huffmanByte.length-1);
			stringBuilder.append(byteToBitString(!flag,huffmanByte[i]));
		}
		//把字符串安装指定的哈夫曼编码进行解码
		
		//把哈夫曼编码表进行调换,反向查询a->100,100->a
		Map<String,Byte> map=new HashMap<String,Byte>();
		for(Map.Entry<Byte, String>entry:huffmanCodes.entrySet()) {
    
    
			map.put(entry.getValue(), entry.getKey());
		}
		//创建一个集合,存放byte
		List<Byte> list=new ArrayList<>();
		for(int i=0;i<stringBuilder.length();) {
    
    
			int count=1;
			boolean flag=true;
			Byte b=null;
			
			while(flag) {
    
    
				//1010100010111...
				//递增取出key1
				String key=stringBuilder.substring(i,i+count);//i不动,让count移动,	指定匹配下一个字符
				b=map.get(key);
				if(b==null) {
    
    //说明没有匹配到
					count++;
				}else {
    
    
					flag=false;
				}
			}
			list.add(b);
			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;
		
	}
	
	/***
	 * 将一个byte转成一个二进制的字符串。
	 * @param flag 标志是否需要补高位,如果是true,表示补高位,反之不补
	 * @param b 传入的byte
	 * @return 是该byte对应的二进制的字符串
	 */
	static String byteToBitString(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;
		}
	}
	/***
	 * 生成哈夫曼编码的总方法
	 * 
	 */
	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/109163695