数据结构之二叉树(七):数据压缩-哈夫曼解码
注释:本文接上篇《数据结构之二叉树(六):数据压缩-哈夫曼编码》
链接:
《数据结构之二叉树(六):数据压缩-哈夫曼编码》
引言:
在上一篇中我们利用哈夫曼编码成功的将目标字符串:
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();
}
}
}