之前利用赫夫曼树来对字符串进行压缩,相当于对字符串加密。现在需要利用赫夫曼编码来进行解码,也就是解密。

如果还不了解赫夫曼编码的小伙伴可以先看上篇文章——利用哈夫曼树实现哈夫曼编码进行字符串压缩

之前利用赫夫曼编码得到了字符串的byte数组,现在首先需要将byte数组中的值转为二进制并且还要转成补码(因为计算机中存的都是二进制补码)

转为二进制的代码如下(有注释):

    /*** 将十进制数转为至少八位的二进制数** @param i    需要转换的十进制数* @param flag 作为是否要补到8位的标志,true表示要补全* @return      将二进制按字符串形式返回*/public static StringBuffer tenToBinary(int i, boolean flag) {//拼接二进制StringBuffer stringBuffer1 = new StringBuffer();StringBuffer stringBuffer2 = new StringBuffer();int temp;//先存储绝对值int cur = Math.abs(i);//得到二进制的反序,之后直接翻转字符串就可以了while (cur != 0) {temp = cur % 2;cur = cur / 2;stringBuffer1.append(temp);}//如果不足八位而且不是数组中的最后一个值则补0while (stringBuffer1.length() < 8 && flag) {stringBuffer1.append(0);}//如果是负数,需要把最高位改成符号位if (i < 0) {String substring = stringBuffer1.substring(0, stringBuffer1.length() - 1);stringBuffer2.append(substring).append(1);} else {stringBuffer2 = stringBuffer1;}//翻转字符串,之前得到是二进制的反序stringBuffer2 = stringBuffer2.reverse();//返回得到的二进制字符串return stringBuffer2;}

再得到二进制的补码:

    /*** 得到十进制数对应的二进数的补码** @param b    十进制数* @param flag 作为是否要补到8位的标志,true表示要补全* @return 字符串形式的二进制补码*/public static String byteToBitString(byte b, boolean flag) {int temp = b;//将temp转为二进制StringBuffer strBinary = tenToBinary(temp, flag);//测试//System.out.println(strBinary);// 如果是负数,得到temp对应的二进制补码,正数三码合一if (temp < 0) {//先转为反码for (int i = 1; i < strBinary.length(); i++) {if (strBinary.charAt(i) == '0') {strBinary = strBinary.replace(i, i + 1, "1");} else {strBinary = strBinary.replace(i, i + 1, "0");}}//再加一变成补码int i = strBinary.length() - 1;//如果是1就需要进位,再置为0while (strBinary.charAt(i) == '1') {strBinary = strBinary.replace(i, i + 1, "0");i--;}//最后别忘了将0改为1strBinary = strBinary.replace(i, i + 1, "1");}return strBinary.toString();}

然后就需要循环将byte数组中的所有数的二进制补码都拿到并且存在可拼接的字符串中,

得到的就是之前用赫夫曼编码压缩成的二进制数

代码如下:

    /*** 将压缩后的编码解开,也就是解码** @param huffmanCodes 赫夫曼编码表 map* @param huffmanBytes 赫夫曼编码得到的字节数组* @param strSize 字符串经过赫夫曼压缩后的大小* @return 就是原来的字符串对应的数组*/private static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes,int strSize) {StringBuffer stringBuffer1 = new StringBuffer();//解码for (int i = 0; i < huffmanBytes.length; i++) {//如果是最后一个值并且二进制个数不能被8整除则不需要补全if (i == huffmanBytes.length - 1 && strSize % 8 != 0)stringBuffer1.append(byteToBitString( huffmanBytes[i],false));elsestringBuffer1.append(byteToBitString(huffmanBytes[i],true));}//测试System.out.println("输出字符串解压成赫夫曼编码后对应的二进制编码:" + stringBuffer1 + "长度为:" + stringBuffer1.length());Map<String ,Byte> map = new HashMap<>();for (Map.Entry<Byte, String> stringByteEntry:huffmanCodes.entrySet()) {map.put(stringByteEntry.getValue(),stringByteEntry.getKey());}//测试//System.out.println(map);List<Byte> list = new ArrayList<>();for (int i = 0; i < stringBuffer1.length();) {int count = 0;Byte b = null;//扫描字符串while (true){//递增的取出keyString key = "";key = stringBuffer1.substring(i,i+count);// 如果map集合中没有对应的二进制编码就count递增b = map.get(key);if (b == null){//说明没取到count++;}else {break;}}list.add(b);i += count;}//循环结束后将list集合中的字符串存储到byte数组中,并返回byte b[] = new byte[list.size()];for (int i = 0; i < b.length; i++) {b[i] = list.get(i);}return b;}

将需要的所有方法封装在一个方法中,便于main方法调用

    /*** 将之前写的方法封装在一个方法之中,便于调用** @param str 原始的字符串* @return 经过赫夫曼编码压缩后对应的字节数组*/public static void huffmanAll(String str) {// 把输入的字符串转为byte数组,在byte数组中存储的是字符对应的ASCII码值byte[] strBytes = str.getBytes();System.out.println(str + ",压缩成赫夫曼编码前对应的byte数组:" + Arrays.toString(strBytes));//计算压缩前的字符串有多少位二进制数int compressionBeforeCodeSize = str.length() * 8 + str.length() - 1;System.out.println(str + ",压缩前的字符串大小:" + compressionBeforeCodeSize);//统计字符串中每个字符出现的次数和空格出现次数并存入Node节点中List<Node> nodeList = totalCharCounts(str);//创建huffman树Node root = createHuffmanTree(nodeList);//得到压缩后的编码getHuffmanCompressionCode(root, "", stringBuffer);//输出赫夫曼编码表System.out.println(str + ",对应的赫夫曼编码表:");System.out.println(huffmanCodes);//得到压缩后的字符串大小int compressionAfterCodeSize = getStrCodeSize();System.out.println(str + ",压缩后的字符串大小:" + compressionAfterCodeSize);//可以算出压缩率是多少double compressionRadio = (compressionBeforeCodeSize - compressionAfterCodeSize) * 1.0 / compressionBeforeCodeSize;System.out.println(str + ",压缩成赫夫曼编码的压缩率为:" + compressionRadio);byte[] bytes = zip(strBytes, huffmanCodes);//解码byte[] decodeByte = decode(huffmanCodes, bytes,compressionAfterCodeSize);System.out.println("解码后的字符串为:" + new String(decodeByte));return ;}

以下是编码与解码的全部方法:

import java.util.*;/*** 实现huffman编码*/
public class HuffmanCode {//将赫夫曼编码表存放在Map<Byte,String>中public static Map<Byte, String> huffmanCodes = new HashMap<>();//需要定义一个StringBuffer来存储某个节点的路径对于的编码public static StringBuffer stringBuffer = new StringBuffer();//创建一个map,来保存每个字符以及他对应出现的次数public static Map<Character, Integer> map = new HashMap<>();public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.println("输入字符串:");//scanner.next()方法不能输入空格,例如输入: aaa bbb实际上只能接收到aaa,空格后面的字符串都接收不到//所以需要用scanner,nextLine()方法来接收字符串String str = scanner.nextLine();//调用总的方法,直接输出解码后的值huffmanAll(str);}/*** 将压缩后的编码解开,也就是解码** @param huffmanCodes 赫夫曼编码表 map* @param huffmanBytes 赫夫曼编码得到的字节数组* @param strSize 字符串经过赫夫曼压缩后的大小* @return 就是原来的字符串对应的数组*/private static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes,int strSize) {StringBuffer stringBuffer1 = new StringBuffer();//解码for (int i = 0; i < huffmanBytes.length; i++) {//如果二进制位的个数能被8整除也if (i == huffmanBytes.length - 1 && strSize % 8 != 0)stringBuffer1.append(byteToBitString( huffmanBytes[i],false));elsestringBuffer1.append(byteToBitString(huffmanBytes[i],true));}//测试System.out.println("输出字符串解压成赫夫曼编码后对应的二进制编码:" + stringBuffer1 + "长度为:" + stringBuffer1.length());Map<String ,Byte> map = new HashMap<>();for (Map.Entry<Byte, String> stringByteEntry:huffmanCodes.entrySet()) {map.put(stringByteEntry.getValue(),stringByteEntry.getKey());}//测试System.out.println(map);List<Byte> list = new ArrayList<>();for (int i = 0; i < stringBuffer1.length();) {int count = 0;Byte b = null;//扫描字符串while (true){//递增的取出keyString key = "";key = stringBuffer1.substring(i,i+count);b = map.get(key);if (b == null){//说明没取到count++;}else {break;}}list.add(b);i += count;}//循环结束后将list集合中的字符串存储到byte数组中,并返回byte b[] = new byte[list.size()];for (int i = 0; i < b.length; i++) {b[i] = list.get(i);}return b;}/*** 得到十进制数对应的二进数的补码** @param b    十进制数* @param flag 作为是否要补到8位的标志,true表示要补全* @return 字符串形式的二进制补码*/public static String byteToBitString(byte b, boolean flag) {int temp = b;//将temp转为二进制StringBuffer strBinary = tenToBinary(temp, flag);//测试//System.out.println(strBinary);// 得到temp对应的二进制补码if (temp < 0) {//先转为反码for (int i = 1; i < strBinary.length(); i++) {if (strBinary.charAt(i) == '0') {strBinary = strBinary.replace(i, i + 1, "1");} else {strBinary = strBinary.replace(i, i + 1, "0");}}//再加一变成补码int i = strBinary.length() - 1;while (strBinary.charAt(i) == '1') {strBinary = strBinary.replace(i, i + 1, "0");i--;}strBinary = strBinary.replace(i, i + 1, "1");}return strBinary.toString();}/*** 将十进制数转为至少八位的二进制数** @param i    需要转换的十进制数* @param flag 作为是否要补到8位的标志,true表示要补全* @return      将二进制按字符串形式返回*/public static StringBuffer tenToBinary(int i, boolean flag) {//拼接二进制StringBuffer stringBuffer1 = new StringBuffer();StringBuffer stringBuffer2 = new StringBuffer();int temp;int cur = Math.abs(i);while (cur != 0) {temp = cur % 2;cur = cur / 2;stringBuffer1.append(temp);}//如果不足八位则补0while (stringBuffer1.length() < 8 && flag) {stringBuffer1.append(0);}if (i < 0) {String substring = stringBuffer1.substring(0, stringBuffer1.length() - 1);stringBuffer2.append(substring).append(1);} else {return stringBuffer1.reverse();}//翻转字符串,之前得到是二进制的反序stringBuffer2 = stringBuffer2.reverse();return stringBuffer2;}/*** 将之前写的方法封装在一个方法之中,便于调用** @param str 原始的字符串* @return 经过赫夫曼编码压缩后对应的字节数组*/public static void huffmanAll(String str) {// 把输入的字符串转为byte数组,在byte数组中存储的是字符对应的ASCII码值byte[] strBytes = str.getBytes();System.out.println(str + ",压缩成赫夫曼编码前对应的byte数组:" + Arrays.toString(strBytes));//计算压缩前的字符串有多少位二进制数int compressionBeforeCodeSize = str.length() * 8 + str.length() - 1;System.out.println(str + ",压缩前的字符串大小:" + compressionBeforeCodeSize);//统计字符串中每个字符出现的次数和空格出现次数并存入Node节点中List<Node> nodeList = totalCharCounts(str);//创建huffman树Node root = createHuffmanTree(nodeList);//得到压缩后的编码getHuffmanCompressionCode(root, "", stringBuffer);//输出赫夫曼编码表System.out.println(str + ",对应的赫夫曼编码表:");System.out.println(huffmanCodes);//得到压缩后的字符串大小int compressionAfterCodeSize = getStrCodeSize();System.out.println(str + ",压缩后的字符串大小:" + compressionAfterCodeSize);//可以算出压缩率是多少double compressionRadio = (compressionBeforeCodeSize - compressionAfterCodeSize) * 1.0 / compressionBeforeCodeSize;System.out.println(str + ",压缩成赫夫曼编码的压缩率为:" + compressionRadio);byte[] bytes = zip(strBytes, huffmanCodes);//解码byte[] decodeByte = decode(huffmanCodes, bytes,compressionAfterCodeSize);System.out.println("解码后的字符串为:" + new String(decodeByte));return ;}/*** @return 得到压缩后的赫夫曼编码大小*/public static int getStrCodeSize() {int size = 0;//将两个map集合都转为set集合Set<Map.Entry<Character, Integer>> mapSet = map.entrySet();Set<Map.Entry<Byte, String>> huffmanMapSet = huffmanCodes.entrySet();//循环两个set集合for (Map.Entry<Character, Integer> set1 : mapSet) {for (Map.Entry<Byte, String> set2 : huffmanMapSet) {//如果两个set的key相同就将他们的value相乘,只是需要注意存储huffman编码中的是字符串,需要乘字符串的长度if ((byte) set1.getKey().charValue() == set2.getKey()) {size = size + set1.getValue() * (set2.getValue().length());//节约时间,之间退出内循环。因为不可能有一对多的关系。break;}}}return size;}/*** 根据huffman树来进行数据编码压缩* 思路:* 1、只要向左子树走就代表0,向右子树走就代表1* 2、从头节点走到对于字符在的节点位置的路径对于的0和1组成的二进制编码就是压缩后该字符对于的编码* 3、需要定义一个StringBuffer来存储某个节点的路径对于的编码* 4、将赫夫曼编码表存放在Map<Byte,String>中** @param node         huffman树的根节点* @param stringBuffer 用于拼接路径* @param code         路径:左子节点是0,右子节点是1* @return*/private static void getHuffmanCompressionCode(Node node, String code, StringBuffer stringBuffer) {StringBuffer stringBuffer1 = new StringBuffer(stringBuffer);stringBuffer1.append(code);//如果为空,不进行处理if (node != null) {//判断node是叶子节点还是非叶子节点if (node.data == null) {//非叶子节点//向左递归getHuffmanCompressionCode(node.left, "0", stringBuffer1);//向右递归getHuffmanCompressionCode(node.right, "1", stringBuffer1);} else {//叶子节点//说明这条路走到尾了,将路径编码存入map中huffmanCodes.put(node.data, stringBuffer1.toString());}}}/*** //统计字符串中每个字符出现的次数和空格出现次数** @param str 字符串* @return 返回一个排好序的Node集合*/public static List<Node> totalCharCounts(String str) {for (int i = 0; i < str.length(); i++) {char ch = str.charAt(i);Integer count = map.get(ch);if (count == null) {count = 0;}map.put(ch, count + 1);}//遍历map,将map中的数据存入Node节点中//先将map转为set集合Set<Map.Entry<Character, Integer>> mapSet = map.entrySet();//观察测试输出//System.out.println(mapSet);List<Node> nodeList = new ArrayList<>();//遍历setfor (Map.Entry<Character, Integer> set : mapSet) {// 将map中的数据存入Node节点中Node node = new Node((byte) set.getKey().charValue(), set.getValue());// 将node存入集合中nodeList.add(node);//System.out.println(set.getKey() + " = " + set.getValue());}//排序Collections.sort(nodeList);//测试//System.out.println(nodeList);return nodeList;}/*** 创建huffman树** @param nodeList 排好序的集合* @return 返回huffman树的根节点*/public static Node createHuffmanTree(List<Node> nodeList) {//循环创建huffman树while (nodeList.size() > 1) {//1、每次取出集合中的前两个节点Node left = nodeList.get(0);Node right = nodeList.get(1);//2、将他们的权值相加构成一个新的节点并作为他们的父节点Node parent = new Node(null, left.weight + right.weight);parent.left = left;parent.right = right;//3、删除已经处理过的节点nodeList.remove(left);nodeList.remove(right);//4、将新的节点存入集合中nodeList.add(parent);//5、重新给集合排序,循环这5步即可,直到集合中只有一个节点,这就是huffman树的根节点Collections.sort(nodeList);//观察测试输出//System.out.println(nodeList);}//返回huffman树的根节点return nodeList.get(0);}/*** 编写一个方法,将字符串对应的byte[]数组,通过生成的赫夫曼编码表,返回一个赫夫曼编码压缩后的byte[]** @param bytes        这时原始的字符串对应的 byte[]* @param huffmanCodes 生成的赫夫曼编码 map* @return 返回赫夫曼编码处理后的 byte[]* 举例: String content = "i like like like java do you like a java"; =》 byte[] contentBytes = content.getBytes();* 返 回 的 是 字 符 串:* "1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000* 101111111100110001001010011011100"* => 对应的 byte[] huffmanCodeBytes ,即 8 位对应一个 byte,放入到 huffmanCodeBytes* huffmanCodeBytes[0] = 10101000(补码) => byte [推导 10101000=> 10101000 - 1 => 10100111(反* 码)=> 11011000(源码) = -88 ]*/public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {//1、先利用赫夫曼编码表将传进来的bytes数组转为压缩后的编码StringBuffer stringBuffer1 = new StringBuffer();for (byte b : bytes) {stringBuffer1.append(huffmanCodes.get(b));}//输出字符串压缩成赫夫曼编码后对应的二进制编码System.out.println("输出字符串压缩成赫夫曼编码后对应的二进制编码:" + stringBuffer1 + "长度为:" + stringBuffer1.length());//获取byte数组的长度,Math.ceil()表示向上取整int len = (int) Math.ceil(stringBuffer1.length() * 1.0 / 8);//也可以用下面的方法获取长度/*if(stringBuffer1.length() % 8 == 0) {len = stringBuffer1.length() / 8;} else {len = stringBuffer1.length() / 8 + 1;}*///测试//System.out.println(stringBuffer1.length());//System.out.println(len);byte[] huffmanBytes = new byte[len];int index = 0;for (int i = 0; i < stringBuffer1.length(); i = i + 8) {String strByte;if (i + 8 > stringBuffer1.length()) {//从i取到字符串最后一个字符strByte = stringBuffer1.substring(i);} else {//一次截取8个strByte = stringBuffer1.substring(i, i + 8);}//将 strByte 转成一个 byte,放入到 huffmanBytes中//该方法是将strByte对应的01字符串传换为十进制//第二个参数表示基数(radix),表示转换为radix进制huffmanBytes[index] = (byte) Integer.parseInt(strByte, 2);index++;}return huffmanBytes;}}

以下是我的测试结果,有些情况会出现bug(实在改不出来了),比如字符串中不能有中文

1、输入的字符串为:i like like like java do you like a java

2、输入的字符串为: hello world java

赫夫曼树的经典应用——赫夫曼编码解码相关推荐

  1. 2020-10-1 //严蔚敏《数据结构》 //赫夫曼树及其应用:创建顺序赫夫曼树创建及得到赫夫曼编码

    //严蔚敏<数据结构> //赫夫曼树及其应用:创建顺序赫夫曼树创建及得到赫夫曼编码 //(从叶子结点到根逆向求每个字符的赫夫曼编码)以及(无栈非递归遍历赫夫曼树,求赫夫曼编码) //自学中 ...

  2. 一文了解赫夫曼树的构建与赫夫曼编码

    文章目录 一.赫夫曼树 基本介绍 赫夫曼树几个重要概念和举例说明 赫夫曼树创建步骤图解 代码构建赫夫曼树 二.赫夫曼编码 1基本介绍 通信领域中的信息的处理方式1-定长编码 通信领域中的信息的处理方式 ...

  3. 赫夫曼树介绍、赫夫曼树的性质、赫夫曼编码、赫夫曼树与赫夫曼编码的应用

    文章目录 赫夫曼树 1. 赫夫曼树介绍: 2. 赫夫曼树的创建过程: 3. 赫夫曼树的性质: 4. 赫夫曼编码: 5. 赫夫曼树与赫夫曼编码的c语言代码实现: 赫夫曼树 1. 赫夫曼树介绍: ​ 赫夫 ...

  4. 赫夫曼树的创建,赫夫曼编码的原理及使用

    目录 一.创建赫夫曼树 代码实现:最后返回值式创建好的赫夫曼树的顶点 对int[] arr = {13, 7, 8, 3, 29, 6, 1};进行赫夫曼树,我们创建好的node数组依次是这样变化 创 ...

  5. 赫夫曼树以及赫夫曼编码实现

    介绍 赫夫曼树创建图解思路 一直找就找到最好的样子: 赫夫曼树创建代码实现 package 树;import java.util.ArrayList; import java.util.Collect ...

  6. 【数据结构】赫夫曼树与赫夫曼编码(可执行完整代码)

    赫夫曼编码对文件进行压缩与解密 理论 赫夫曼树 赫夫曼编码 应用 应用源码 运行结果截图 理论 赫夫曼树 先有赫夫曼树,才有赫夫曼编码.所以,首先简单介绍一下什么是赫夫曼树. 假设一共五个叶子节点,分 ...

  7. 树结构之--赫夫曼树

    1.什么是赫夫曼树: 给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree).哈夫曼树是带权路径长度最短的树 ...

  8. 数据结构--赫夫曼树

    数据结构 –赫夫曼树 文章目录 数据结构 一.一些概念 二.最优二叉树(赫夫曼树) 三.赫夫曼树的构造 四.赫夫曼编码 五.前缀编码 一.一些概念 路径:从树中一个结点到另一个结点之间的分支构成这两个 ...

  9. 赫夫曼树、赫夫曼编码

    基本介绍 给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为赫夫曼树(HuffmanTree). 赫夫曼树是带权路径长度最短的树, ...

最新文章

  1. 关注中国的房地产市场
  2. 13、Java Swing事件监听:事件处理模型和事件监听器
  3. java中解决脏读_java并发编程学习之脏读代码示例及处理
  4. NOIP信息奥赛--1995“同创杯”初中复赛题题解(五)
  5. python3 image_python3 ImageTk 安装方法
  6. 设计灵感|时尚潮流品牌如何通过网页设计呈现
  7. 34(数组). 在排序数组中查找元素的第一个和最后一个位置
  8. 没有工厂模式(工厂模式2)
  9. ArrayUtils
  10. 计算机 映射网络驱动器,win7电脑映射网络驱动器的方法?
  11. 强化学习从入门到放弃的资料
  12. 电热玻璃水壶CE认证检测标准介绍
  13. 消费者满意度调查方案
  14. 自动驾驶领域的Android?百度能否超越特斯拉和谷歌
  15. Linux延时(延迟)函数比较:介绍Linux系统中常用的延时函数sleep、usleep、nanosleep、select和std::sleep_for()的区别和使用场景
  16. go-viper实践
  17. Learning NGINX 学习NGINX Lynda课程中文字幕
  18. Windows上鲜为人知的三款黑马软件,款款深入人心
  19. java package包和import 与class打包成jar文件
  20. 大数据Hadoop视频教程

热门文章

  1. Kali Linux 基础篇深度信息挖掘工具Dmitry使用技巧
  2. MICMIC-IV 个人查询策略(+官方查询语句注释)
  3. html页面控制台输出,浏览器javascript输出控制台在哪?
  4. 从标题到活动谈谈微信运营三件事儿
  5. 美食杰项目---个人页和他人主页
  6. Microsoft Visual Studio Key
  7. 什么是死锁?死锁产生的条件?
  8. 音频振动数据采集卡的技术参数-阿尔泰科技
  9. 服务器虚拟化 目标,服务器虚拟化之虚拟机和模板介绍(49页)-原创力文档
  10. 证明莫比乌斯函数为积性