学习地址:【数据结构与算法基础-java版】 🚀数据结构--Java专栏🚀
- 笔记01【01-09】【概述、数组基本使用】【源码、课件】
- 笔记02【10-18】【栈、队列、单链表(增删节点)、循环链表、双向循环链表、递归(斐波那契、汉诺塔)】
- 笔记03【19-27】【(时间、空间复杂度);八大排序(冒泡、快速、插入、希尔、选择、归并、基数、队列基数)】
- 笔记04【28-33】【树结构(二叉树)概述、创建、遍历、查找节点、删除节点】
- 笔记05【34-39】【顺序存储二叉树概述、二叉树遍历、堆排序、线索二叉树实现及遍历】
- 笔记06【40-48】【赫夫曼树、概述、原理分析、代码实现(数据压缩、创建编码表、解码、压缩文件、解压文件)】
- 笔记07【49-54】【二叉排序树(添加、查找、删除节点)】
- 笔记08【55-57】【二叉平衡树(AVL)-概述、单旋转、双旋转】
- 笔记09【58-60】【计算机中数据的存储原理、2-3树的插入原理、B树和B+树】
- 笔记10【61-63】【哈希表概述、散列函数的设计、散列冲突解决方案】
- 笔记11【64-67】【图结构概述、图遍历原理(BFS\DFS)、图遍历代码实现】
目 录
P40-4.13 赫夫曼树概述
最优二叉树(赫夫曼树):它是n个带权叶子结点构成的所有二叉树中,带权路径长度最小的二叉树。
P41-4.14 创建赫夫曼树的流程分析
赫夫曼树
P42-4.15 代码实现创建赫夫曼树
Alt + Shift + S【快捷键】
1、Node.java
package demo9;
public class Node implements Comparable<Node> {
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
@Override
public int compareTo(Node o) {
return -(this.value - o.value);// 加“-”,大在前
}
// Alt + Shift + S【快捷键】
@Override
public String toString() {
return "Node [value=" + value + "]";
}
}
2、TestHuffmanTree.java
package demo9;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TestHuffmanTree {
public static void main(String[] args) {
int[] arr = { 3, 7, 8, 29, 5, 11, 23, 14 };
Node node = createHuffmanTree(arr);
System.out.println(node);
}
// 创建赫夫曼树
public static Node createHuffmanTree(int[] arr) {
// 1、先使用数组中所有的元素创建若干个二叉树,(只有一个节点)
List<Node> nodes = new ArrayList<>();// 创建数组集合,用来存二叉树
for (int value : arr) { // for循环的另外一种表达,遍历数组元素用的
nodes.add(new Node(value));
}
// Collections.sort(nodes);
// System.out.println(nodes);
// 循环处理!!!
while (nodes.size() > 1) { // 只有1颗树时, 结束处理!
// 2、排序【排序前提:Node可以排序!】
Collections.sort(nodes);// 需要Node实现接口
// 3、取出来权值最小的两个二叉树
// 3.1、取出最权值最小的二叉树
Node left = nodes.get(nodes.size() - 1);
// 3.2、取出最权值次小的二叉树
Node right = nodes.get(nodes.size() - 2);
// 4、创建一颗新的二叉树
Node parent = new Node(left.value + right.value);
// 5、把取出来的两个二叉树移除
nodes.remove(left);
nodes.remove(right);
// 6、放入原来的二叉树集合中
nodes.add(parent);
}
return nodes.get(0);
}
}
P43-4.16 赫夫曼编码原理分析
1、通信领域中信息的处理1-定长编码
赫夫曼编码的运用
计算机并不能直接将字符发给别人。
先将 字符 按照某个标准(ASCII)转换为数字。【c-->99,a-->97】
【计算机只能识别0、1串,每一个数字都是一个byte(字节)】
再将 数字 转换为 8位的字节,
【1100011--->99--->c】
将01串发给另一台计算机B,计算机B按 8位 划分 01串,
将 01串 转换为 十进制数字,再将 十进制数字 根据 ASCII 转换为 字母。
定长编码方式,传输效率 极低!!!
固定长度~浪费空间 ==> 非定长编码
2、通信领域中信息的处理2-非定长编码
先对计算串进行处理,统计字符串中每个字符出现了多少次,
令经常出现的字符长度长一些,不经常出现的字符长度短一些,【不按照ASCII进行处理】
将次数当作权值构造哈夫曼!!!
3、通信领域中信息的处理3-赫夫曼编码
字符出现次数少的,在树底;
字符出现次数多的,在树顶。
左0 右1
将 字符串 按照 赫夫曼编码表 进行编码,
按照 编码表 进行 解码,无损压缩!
P44-4.17 数据压缩之创建赫夫曼树
1、Map从入门到性能分析
Java---Map从入门到性能分析
https://blog.csdn.net/weixin_44949135/article/details/106862811
2、思路分析
1、统计字符出现次数,并排序;
2、根据排序结果,将字符转换为节点;
3、将节点转换为赫夫曼树;
字符 可以转换为 byte。
// 节点 要存储 字符(data) 与 权值(weight-字符出现的次数)
// 每个字符作为一个节点
// 按照 权值(weight-字符出现的次数) 进行 排序
Byte data;// 存储节点代表的字符/英文字符可转byte
int weight;// 权值
Node left;// 左节点
Node right;// 右节点
// 创建赫夫曼树的时候,新创建的节点,无data,只有weight data可为空
不 对 字符串 进行 编码,对 字符串的byte数组 进行 编码。
所有的数据 都 可以 转换为 byte数组。【将 数据 转换为 byte数组--->文件操作!!!】
3、代码实现
3.1、Node.java
package demo10;
public class Node implements Comparable<Node> {
// 节点 要存储 字符(data) 与 权值(weight-字符出现的次数)
// 每个字符作为一个节点
// 按照 权值(weight-字符出现的次数) 进行 排序
Byte data;// 存储节点代表的字符/英文字符可转byte
int weight;// 权值
Node left;// 左节点
Node right;// 右节点
// 创建赫夫曼树的时候,新创建的节点,无data,只有weight|data可为空
public Node(Byte data, int weight) {
this.data = data;
this.weight = weight;
}
@Override
public String toString() {
return "Node [data=" + data + ", weight=" + weight + "]";
}
@Override
public int compareTo(Node o) {
return o.weight - this.weight;//倒序
}
}
3.2、TestHuffmanCode44.java
package demo10;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TestHuffmanCode44 {
public static void main(String[] args) {
String msg = "can you can a can as a can canner can a can.";
/**
* 赫夫曼编码 不直接对字符串进行编码。而是编码字符串的byte数组。
* 所有的数据都可以转换为byte数组。文件压缩使用byte数组。
* 文件读取出来是一个byte数组。所以,将数据转换为byte数组。
*/
byte[] bytes = msg.getBytes();
byte[] b = huffmanZip(bytes);// 获取使用赫夫曼编码压缩后的byte数组
}
/**
* 进行赫夫曼编码压缩的方法
*
* @param bytes
* @return
*/
private static byte[] huffmanZip(byte[] bytes) {
// 1、先统计每一个byte出现的次数,并放入一个集合中【getNodes()】
List<Node> nodes = getNodes(bytes);
// 2、创建一棵赫夫曼树
Node tree = createHuffmanTree(nodes);
System.out.println(tree);
System.out.println(tree.right);
System.out.println(tree.left);
// 3、创建一个赫夫曼编码表
// 4、编码
return null;
}
/**
* 1、把byte数组转为node集合
*
* @param bytes
* @return
*/
private static List<Node> getNodes(byte[] bytes) {
List<Node> nodes = new ArrayList<>();
// 存储每一个byte出现了多少次。
Map<Byte, Integer> counts = new HashMap<>();
// 统计每一个byte出现的次数
for (byte b : bytes) {
Integer count = counts.get(b);
if (count == null) {
counts.put(b, 1);
} else {
counts.put(b, count + 1);
}
}
//System.out.println(counts);
//{32=11, 97=11, 114=1, 99=7, 115=1, 117=1, 101=1, 121=1, 110=8, 46=1, 111=1}
// 32-空格、97-a
// 把每一个键值对转为一个node对象
for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
nodes.add(new Node(entry.getKey(), entry.getValue()));
}
return nodes;
}
/**
* 2、创建赫夫曼树
*
* @param nodes
* @return
*/
private static Node createHuffmanTree(List<Node> nodes) {
while (nodes.size() > 1) {
// 排序
Collections.sort(nodes);
// System.out.println(nodes);
// 取出两个权值最低的二叉树
Node left = nodes.get(nodes.size() - 1);
Node right = nodes.get(nodes.size() - 2);
// 创建一颗新的二叉树
Node parent = new Node(null, left.weight + right.weight);
// 把之前取出来的两颗二叉树设置为新创建的二叉树的子树
parent.left = left;
parent.right = right;
// 把前面取出来的两颗二叉树删除
nodes.remove(left);
nodes.remove(right);
// 把新创建的二叉树放入集合中
nodes.add(parent);
}
return nodes.get(0);
}
}
P45-4.18 数据压缩之创建编码表&编码
1、TestHuffmanCode45.java
package demo10;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TestHuffmanCode45 {
public static void main(String[] args) {
String msg = "can you can a can as a can canner can a can.";
/**
* 赫夫曼编码 不直接对字符串进行编码。而是编码字符串的byte数组。
* 所有的数据都可以转换为byte数组。文件压缩使用byte数组。
* 文件读取出来是一个byte数组。所以,将数据转换为byte数组。
*/
byte[] bytes = msg.getBytes();
byte[] b = huffmanZip(bytes);// 获取使用赫夫曼编码压缩后的byte数组
System.out.println(bytes.length);
System.out.println(b.length);
}
/**
* 进行赫夫曼编码压缩的方法
*
* @param bytes
* @return
*/
private static byte[] huffmanZip(byte[] bytes) {
// 1、先统计每一个byte出现的次数,并放入一个集合中【getNodes()】
List<Node> nodes = getNodes(bytes);
// 2、创建一棵赫夫曼树
Node tree = createHuffmanTree(nodes);
System.out.println(tree);
System.out.println(tree.right);
System.out.println(tree.left);
// 3、创建一个赫夫曼编码表
Map<Byte, String> huffCodes = getCodes(tree);
System.out.println(huffCodes);
// 4、编码
byte[] b = zip(bytes, huffCodes);
return b;
}
/**
* 1、把byte数组转为node集合
*
* @param bytes
* @return
*/
private static List<Node> getNodes(byte[] bytes) {
List<Node> nodes = new ArrayList<>();
// 存储每一个byte出现了多少次。
Map<Byte, Integer> counts = new HashMap<>();
// 统计每一个byte出现的次数
for (byte b : bytes) {
Integer count = counts.get(b);
if (count == null) {
counts.put(b, 1);
} else {
counts.put(b, count + 1);
}
}
//System.out.println(counts);
//{32=11, 97=11, 114=1, 99=7, 115=1, 117=1, 101=1, 121=1, 110=8, 46=1, 111=1}
// 32-空格、97-a
// 把每一个键值对转为一个node对象
for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
nodes.add(new Node(entry.getKey(), entry.getValue()));
}
return nodes;
}
/**
* 2、创建赫夫曼树
*
* @param nodes
* @return
*/
private static Node createHuffmanTree(List<Node> nodes) {
while (nodes.size() > 1) {
// 排序
Collections.sort(nodes);
// System.out.println(nodes);
// 取出两个权值最低的二叉树
Node left = nodes.get(nodes.size() - 1);
Node right = nodes.get(nodes.size() - 2);
// 创建一颗新的二叉树
Node parent = new Node(null, left.weight + right.weight);
// 把之前取出来的两颗二叉树设置为新创建的二叉树的子树
parent.left = left;
parent.right = right;
// 把前面取出来的两颗二叉树删除
nodes.remove(left);
nodes.remove(right);
// 把新创建的二叉树放入集合中
nodes.add(parent);
}
return nodes.get(0);
}
// 用于临时存储路径
static StringBuilder sb = new StringBuilder();
// 用于存储赫夫曼编码
static Map<Byte, String> huffCodes = new HashMap<>();
/**
* 3、根据赫夫曼树获取赫夫曼编码
* 需要保存前面树支的结果0、1
* @param tree
* @return
*/
private static Map<Byte, String> getCodes(Node tree) {
if (tree == null) {
return null;
}
getCodes(tree.left, "0", sb);
getCodes(tree.right, "1", sb);
return huffCodes;
}
private static void getCodes(Node node, String code, StringBuilder sb) {
StringBuilder sb2 = new StringBuilder(sb);
sb2.append(code);
if (node.data == null) {
getCodes(node.left, "0", sb2);
getCodes(node.right, "1", sb2);
} else {
huffCodes.put(node.data, sb2.toString());
}
}
/**
* 4、进行赫夫曼编码
*
* @param bytes
* @param huffCodes2
* @return
*/
private static byte[] zip(byte[] bytes, Map<Byte, String> huffCodes) {
StringBuilder sb = new StringBuilder();
// 把需要压缩的byte数组处理成一个二进制的字符串
for (byte b : bytes) {
sb.append(huffCodes.get(b));
}
// System.out.println(sb.toString());
// 定义长度
int len;
if (sb.length() % 8 == 0) {
len = sb.length() / 8;
} else {
len = sb.length() / 8 + 1;
}
// 用于存储压缩后的byte
byte[] by = new byte[len];
// 记录新byte的位置
int index = 0;
for (int i = 0; i < sb.length(); i += 8) {
String strByte;
if (i + 8 > sb.length()) {
strByte = sb.substring(i);
} else {
strByte = sb.substring(i, i + 8);
}
byte byt = (byte) Integer.parseInt(strByte, 2);//二进制转十进制
System.out.println(strByte + " : " + byt);
by[index] = byt;
index++;
}
return by;
}
}
P46-4.19 使用赫夫曼编码进行解码
package demo10;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TestHuffmanCode45 {
public static void main(String[] args) {
String msg = "can you can a can as a can canner can a can.";
/**
* 赫夫曼编码 不直接对字符串进行编码。而是编码字符串的byte数组。
* 所有的数据都可以转换为byte数组。文件压缩使用byte数组。
* 文件读取出来是一个byte数组。所以,将数据转换为byte数组。
*/
byte[] bytes = msg.getBytes();
//进行赫夫曼编码压缩
byte[] b = huffmanZip(bytes);// 获取使用赫夫曼编码压缩后的byte数组
System.out.println(bytes.length);
System.out.println(b.length);
//使用赫夫曼编码进行解码
byte[] newBytes = decode(huffCodes,b);
System.out.println(new String(newBytes));
}
/**
* 进行赫夫曼编码压缩的方法
*
* @param bytes
* @return
*/
private static byte[] huffmanZip(byte[] bytes) {
// 1、先统计每一个byte出现的次数,并放入一个集合中【getNodes()】
List<Node> nodes = getNodes(bytes);
// 2、创建一棵赫夫曼树
Node tree = createHuffmanTree(nodes);
System.out.println(tree);
System.out.println(tree.right);
System.out.println(tree.left);
// 3、创建一个赫夫曼编码表
Map<Byte, String> huffCodes = getCodes(tree);
System.out.println(huffCodes);
// 4、编码
byte[] b = zip(bytes, huffCodes);
return b;
}
/**
* 1、把byte数组转为node集合
*
* @param bytes
* @return
*/
private static List<Node> getNodes(byte[] bytes) {
List<Node> nodes = new ArrayList<>();
// 存储每一个byte出现了多少次。
Map<Byte, Integer> counts = new HashMap<>();
// 统计每一个byte出现的次数
for (byte b : bytes) {
Integer count = counts.get(b);
if (count == null) {
counts.put(b, 1);
} else {
counts.put(b, count + 1);
}
}
//System.out.println(counts);
//{32=11, 97=11, 114=1, 99=7, 115=1, 117=1, 101=1, 121=1, 110=8, 46=1, 111=1}
// 32-空格、97-a
// 把每一个键值对转为一个node对象
for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
nodes.add(new Node(entry.getKey(), entry.getValue()));
}
return nodes;
}
/**
* 2、创建赫夫曼树
*
* @param nodes
* @return
*/
private static Node createHuffmanTree(List<Node> nodes) {
while (nodes.size() > 1) {
// 排序
Collections.sort(nodes);
// System.out.println(nodes);
// 取出两个权值最低的二叉树
Node left = nodes.get(nodes.size() - 1);
Node right = nodes.get(nodes.size() - 2);
// 创建一颗新的二叉树
Node parent = new Node(null, left.weight + right.weight);
// 把之前取出来的两颗二叉树设置为新创建的二叉树的子树
parent.left = left;
parent.right = right;
// 把前面取出来的两颗二叉树删除
nodes.remove(left);
nodes.remove(right);
// 把新创建的二叉树放入集合中
nodes.add(parent);
}
return nodes.get(0);
}
// 用于临时存储路径
static StringBuilder sb = new StringBuilder();
// 用于存储赫夫曼编码
static Map<Byte, String> huffCodes = new HashMap<>();
/**
* 3、根据赫夫曼树获取赫夫曼编码
* 需要保存前面树支的结果0、1
* @param tree
* @return
*/
private static Map<Byte, String> getCodes(Node tree) {
if (tree == null) {
return null;
}
getCodes(tree.left, "0", sb);
getCodes(tree.right, "1", sb);
return huffCodes;
}
private static void getCodes(Node node, String code, StringBuilder sb) {
StringBuilder sb2 = new StringBuilder(sb);
sb2.append(code);
if (node.data == null) {
getCodes(node.left, "0", sb2);
getCodes(node.right, "1", sb2);
} else {
huffCodes.put(node.data, sb2.toString());
}
}
/**
* 4、进行赫夫曼编码
*
* @param bytes
* @param huffCodes2
* @return
*/
private static byte[] zip(byte[] bytes, Map<Byte, String> huffCodes) {
StringBuilder sb = new StringBuilder();
// 把需要压缩的byte数组处理成一个二进制的字符串
for (byte b : bytes) {
sb.append(huffCodes.get(b));
}
// System.out.println(sb.toString());
// 定义长度
int len;
if (sb.length() % 8 == 0) {
len = sb.length() / 8;
} else {
len = sb.length() / 8 + 1;
}
// 用于存储压缩后的byte
byte[] by = new byte[len];
// 记录新byte的位置
int index = 0;
for (int i = 0; i < sb.length(); i += 8) {
String strByte;
if (i + 8 > sb.length()) {
strByte = sb.substring(i);
} else {
strByte = sb.substring(i, i + 8);
}
byte byt = (byte) Integer.parseInt(strByte, 2);//二进制转十进制
System.out.println(strByte + " : " + byt);
by[index] = byt;
index++;
}
return by;
}
/**
* 使用指定的赫夫曼编码表进行解码
*
* @param huffCodes2
* @param b
* @return
*/
private static byte[] decode(Map<Byte, String> huffCodes, byte[] bytes) {
StringBuilder sb = new StringBuilder();
// 把byte数组转为一个二进制的字符串
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
// 是否是最后一个。
boolean flag = (i == bytes.length - 1);
sb.append(byteToBitStr(!flag, b));
}
// 把字符串按照指定的赫夫曼编码进行解码
// 把赫夫曼编码的键值对进行调换
Map<String, Byte> map = new HashMap<>();
for (Map.Entry<Byte, String> entry : huffCodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
// 创建一个集合,用于存byte
List<Byte> list = new ArrayList<>();
// 处理字符串
for (int i = 0; i < sb.length();) {
int count = 1;
boolean flag = true;
Byte b = null;
// 截取出一个byte
while (flag) {
String key = sb.substring(i, i + count);
b = map.get(key);
if (b == null) {
count++;
} else {
flag = false;
}
}
list.add(b);
i += count;
}
// 把集合转为数组
byte[] b = new byte[list.size()];
for (int i = 0; i < b.length; i++) {
b[i] = list.get(i);
}
return b;
}
/**
* 转为8位的字符串
* @param flag
* @param b
* @return
*/
private static String byteToBitStr(boolean flag, byte b) {
int temp = b;
if (flag) {
temp |= 256;//按位或 256
}
String str = Integer.toBinaryString(temp);//返回int变量的二进制表示的字符串。
if (flag) {
return str.substring(str.length() - 8);
} else {
return str;
}
}
}
P47-4.20 使用赫夫曼编码压缩文件
运行项目后,选中项目,进行刷新。
package demo10;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TestHuffmanCode45 {
public static void main(String[] args) {
// String msg = "can you can a can as a can canner can a can.";
// /**
// * 赫夫曼编码 不直接对字符串进行编码。而是编码字符串的byte数组。
// * 所有的数据都可以转换为byte数组。文件压缩使用byte数组。
// * 文件读取出来是一个byte数组。所以,将数据转换为byte数组。
// */
// byte[] bytes = msg.getBytes();
// //进行赫夫曼编码压缩
// byte[] b = huffmanZip(bytes);// 获取使用赫夫曼编码压缩后的byte数组
// System.out.println(bytes.length);
// System.out.println(b.length);
// //使用赫夫曼编码进行解码
// byte[] newBytes = decode(huffCodes,b);
// System.out.println(new String(newBytes));
String src = "1.png";
String dest = "2.zip";
try{
zipFile(src, dest);
} catch(IOException e){
e.printStackTrace();
}
}
/**
* 压缩文件
*
* @param src
* @param dst
* @throws IOException
*/
public static void zipFile(String src, String dst) throws IOException {
// 创建一个输入流
InputStream is = new FileInputStream(src);
// 创建一个和输入流指向的文件大小一样的byte数组
byte[] b = new byte[is.available()];//available()获取读的文件所有的字节个数
// 读取文件内容
is.read(b);
is.close();
// 使用赫夫曼编码进行编码
byte[] byteZip = huffmanZip(b);
System.out.println(b.length);
System.out.println(byteZip.length);
// 输出流
OutputStream os = new FileOutputStream(dst);
ObjectOutputStream oos = new ObjectOutputStream(os);
// 把压缩后的byte数组写入文件
oos.writeObject(byteZip);
// 把赫夫曼编码表写入文件
oos.writeObject(huffCodes);
oos.close();
os.close();
}
/**
* 进行赫夫曼编码压缩的方法
*
* @param bytes
* @return
*/
private static byte[] huffmanZip(byte[] bytes) {
// 1、先统计每一个byte出现的次数,并放入一个集合中【getNodes()】
List<Node> nodes = getNodes(bytes);
// 2、创建一棵赫夫曼树
Node tree = createHuffmanTree(nodes);
System.out.println(tree);
System.out.println(tree.right);
System.out.println(tree.left);
// 3、创建一个赫夫曼编码表
Map<Byte, String> huffCodes = getCodes(tree);
System.out.println(huffCodes);
// 4、编码
byte[] b = zip(bytes, huffCodes);
return b;
}
/**
* 1、把byte数组转为node集合
*
* @param bytes
* @return
*/
private static List<Node> getNodes(byte[] bytes) {
List<Node> nodes = new ArrayList<>();
// 存储每一个byte出现了多少次。
Map<Byte, Integer> counts = new HashMap<>();
// 统计每一个byte出现的次数
for (byte b : bytes) {
Integer count = counts.get(b);
if (count == null) {
counts.put(b, 1);
} else {
counts.put(b, count + 1);
}
}
//System.out.println(counts);
//{32=11, 97=11, 114=1, 99=7, 115=1, 117=1, 101=1, 121=1, 110=8, 46=1, 111=1}
// 32-空格、97-a
// 把每一个键值对转为一个node对象
for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
nodes.add(new Node(entry.getKey(), entry.getValue()));
}
return nodes;
}
/**
* 2、创建赫夫曼树
*
* @param nodes
* @return
*/
private static Node createHuffmanTree(List<Node> nodes) {
while (nodes.size() > 1) {
// 排序
Collections.sort(nodes);
// System.out.println(nodes);
// 取出两个权值最低的二叉树
Node left = nodes.get(nodes.size() - 1);
Node right = nodes.get(nodes.size() - 2);
// 创建一颗新的二叉树
Node parent = new Node(null, left.weight + right.weight);
// 把之前取出来的两颗二叉树设置为新创建的二叉树的子树
parent.left = left;
parent.right = right;
// 把前面取出来的两颗二叉树删除
nodes.remove(left);
nodes.remove(right);
// 把新创建的二叉树放入集合中
nodes.add(parent);
}
return nodes.get(0);
}
// 用于临时存储路径
static StringBuilder sb = new StringBuilder();
// 用于存储赫夫曼编码
static Map<Byte, String> huffCodes = new HashMap<>();
/**
* 3、根据赫夫曼树获取赫夫曼编码
* 需要保存前面树支的结果0、1
* @param tree
* @return
*/
private static Map<Byte, String> getCodes(Node tree) {
if (tree == null) {
return null;
}
getCodes(tree.left, "0", sb);
getCodes(tree.right, "1", sb);
return huffCodes;
}
private static void getCodes(Node node, String code, StringBuilder sb) {
StringBuilder sb2 = new StringBuilder(sb);
sb2.append(code);
if (node.data == null) {
getCodes(node.left, "0", sb2);
getCodes(node.right, "1", sb2);
} else {
huffCodes.put(node.data, sb2.toString());
}
}
/**
* 4、进行赫夫曼编码
*
* @param bytes
* @param huffCodes2
* @return
*/
private static byte[] zip(byte[] bytes, Map<Byte, String> huffCodes) {
StringBuilder sb = new StringBuilder();
// 把需要压缩的byte数组处理成一个二进制的字符串
for (byte b : bytes) {
sb.append(huffCodes.get(b));
}
// System.out.println(sb.toString());
// 定义长度
int len;
if (sb.length() % 8 == 0) {
len = sb.length() / 8;
} else {
len = sb.length() / 8 + 1;
}
// 用于存储压缩后的byte
byte[] by = new byte[len];
// 记录新byte的位置
int index = 0;
for (int i = 0; i < sb.length(); i += 8) {
String strByte;
if (i + 8 > sb.length()) {
strByte = sb.substring(i);
} else {
strByte = sb.substring(i, i + 8);
}
byte byt = (byte) Integer.parseInt(strByte, 2);//二进制转十进制
System.out.println(strByte + " : " + byt);
by[index] = byt;
index++;
}
return by;
}
/**
* 使用指定的赫夫曼编码表进行解码
*
* @param huffCodes2
* @param b
* @return
*/
private static byte[] decode(Map<Byte, String> huffCodes, byte[] bytes) {
StringBuilder sb = new StringBuilder();
// 把byte数组转为一个二进制的字符串
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
// 是否是最后一个。
boolean flag = (i == bytes.length - 1);
sb.append(byteToBitStr(!flag, b));
}
// 把字符串按照指定的赫夫曼编码进行解码
// 把赫夫曼编码的键值对进行调换
Map<String, Byte> map = new HashMap<>();
for (Map.Entry<Byte, String> entry : huffCodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
// 创建一个集合,用于存byte
List<Byte> list = new ArrayList<>();
// 处理字符串
for (int i = 0; i < sb.length();) {
int count = 1;
boolean flag = true;
Byte b = null;
// 截取出一个byte
while (flag) {
String key = sb.substring(i, i + count);
b = map.get(key);
if (b == null) {
count++;
} else {
flag = false;
}
}
list.add(b);
i += count;
}
// 把集合转为数组
byte[] b = new byte[list.size()];
for (int i = 0; i < b.length; i++) {
b[i] = list.get(i);
}
return b;
}
/**
* 转为8位的字符串
* @param flag
* @param b
* @return
*/
private static String byteToBitStr(boolean flag, byte b) {
int temp = b;
if (flag) {
temp |= 256;//按位或 256
}
String str = Integer.toBinaryString(temp);//返回int变量的二进制表示的字符串。
if (flag) {
return str.substring(str.length() - 8);
} else {
return str;
}
}
}
P48-4.21 使用赫夫曼编码解压文件
package demo10;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TestHuffmanCode45 {
public static void main(String[] args) {
// String msg = "can you can a can as a can canner can a can.";
// /**
// * 赫夫曼编码 不直接对字符串进行编码。而是编码字符串的byte数组。
// * 所有的数据都可以转换为byte数组。文件压缩使用byte数组。
// * 文件读取出来是一个byte数组。所以,将数据转换为byte数组。
// */
// byte[] bytes = msg.getBytes();
// //进行赫夫曼编码压缩
// byte[] b = huffmanZip(bytes);// 获取使用赫夫曼编码压缩后的byte数组
// System.out.println(bytes.length);
// System.out.println(b.length);
// //使用赫夫曼编码进行解码
// byte[] newBytes = decode(huffCodes,b);
// System.out.println(new String(newBytes));
String src = "1.png";
String dest = "2.zip";
// try {
// zipFile(src, dest);
// } catch (IOException e) {
// e.printStackTrace();
// }
try {
unZip("2.zip", "3.png");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 文件的解压
*
* @param src
* @param dst
* @throws Exception
*/
public static void unZip(String src, String dst) throws Exception {
// 创建一个输入流
InputStream is = new FileInputStream("2.zip");
ObjectInputStream ois = new ObjectInputStream(is);
// 读取byte数组
byte[] b = (byte[]) ois.readObject();
// 读取赫夫曼编码表
Map<Byte, String> codes = (Map<Byte, String>) ois.readObject();
ois.close();
is.close();
// 解码
byte[] bytes = decode(codes, b);
// 创建一个输出流
OutputStream os = new FileOutputStream(dst);
// 写出数据
os.write(bytes);
os.close();
}
/**
* 压缩文件
*
* @param src
* @param dst
* @throws IOException
*/
public static void zipFile(String src, String dst) throws IOException {
// 创建一个输入流
InputStream is = new FileInputStream(src);
// 创建一个和输入流指向的文件大小一样的byte数组
byte[] b = new byte[is.available()];//available()获取读的文件所有的字节个数
// 读取文件内容
is.read(b);
is.close();
// 使用赫夫曼编码进行编码
byte[] byteZip = huffmanZip(b);
System.out.println(b.length);
System.out.println(byteZip.length);
// 输出流
OutputStream os = new FileOutputStream(dst);
ObjectOutputStream oos = new ObjectOutputStream(os);
// 把压缩后的byte数组写入文件
oos.writeObject(byteZip);
// 把赫夫曼编码表写入文件
oos.writeObject(huffCodes);
oos.close();
os.close();
}
/**
* 进行赫夫曼编码压缩的方法
*
* @param bytes
* @return
*/
private static byte[] huffmanZip(byte[] bytes) {
// 1、先统计每一个byte出现的次数,并放入一个集合中【getNodes()】
List<Node> nodes = getNodes(bytes);
// 2、创建一棵赫夫曼树
Node tree = createHuffmanTree(nodes);
System.out.println(tree);
System.out.println(tree.right);
System.out.println(tree.left);
// 3、创建一个赫夫曼编码表
Map<Byte, String> huffCodes = getCodes(tree);
System.out.println(huffCodes);
// 4、编码
byte[] b = zip(bytes, huffCodes);
return b;
}
/**
* 1、把byte数组转为node集合
*
* @param bytes
* @return
*/
private static List<Node> getNodes(byte[] bytes) {
List<Node> nodes = new ArrayList<>();
// 存储每一个byte出现了多少次。
Map<Byte, Integer> counts = new HashMap<>();
// 统计每一个byte出现的次数
for (byte b : bytes) {
Integer count = counts.get(b);
if (count == null) {
counts.put(b, 1);
} else {
counts.put(b, count + 1);
}
}
//System.out.println(counts);
//{32=11, 97=11, 114=1, 99=7, 115=1, 117=1, 101=1, 121=1, 110=8, 46=1, 111=1}
// 32-空格、97-a
// 把每一个键值对转为一个node对象
for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
nodes.add(new Node(entry.getKey(), entry.getValue()));
}
return nodes;
}
/**
* 2、创建赫夫曼树
*
* @param nodes
* @return
*/
private static Node createHuffmanTree(List<Node> nodes) {
while (nodes.size() > 1) {
// 排序
Collections.sort(nodes);
// System.out.println(nodes);
// 取出两个权值最低的二叉树
Node left = nodes.get(nodes.size() - 1);
Node right = nodes.get(nodes.size() - 2);
// 创建一颗新的二叉树
Node parent = new Node(null, left.weight + right.weight);
// 把之前取出来的两颗二叉树设置为新创建的二叉树的子树
parent.left = left;
parent.right = right;
// 把前面取出来的两颗二叉树删除
nodes.remove(left);
nodes.remove(right);
// 把新创建的二叉树放入集合中
nodes.add(parent);
}
return nodes.get(0);
}
// 用于临时存储路径
static StringBuilder sb = new StringBuilder();
// 用于存储赫夫曼编码
static Map<Byte, String> huffCodes = new HashMap<>();
/**
* 3、根据赫夫曼树获取赫夫曼编码
* 需要保存前面树支的结果0、1
* @param tree
* @return
*/
private static Map<Byte, String> getCodes(Node tree) {
if (tree == null) {
return null;
}
getCodes(tree.left, "0", sb);
getCodes(tree.right, "1", sb);
return huffCodes;
}
private static void getCodes(Node node, String code, StringBuilder sb) {
StringBuilder sb2 = new StringBuilder(sb);
sb2.append(code);
if (node.data == null) {
getCodes(node.left, "0", sb2);
getCodes(node.right, "1", sb2);
} else {
huffCodes.put(node.data, sb2.toString());
}
}
/**
* 4、进行赫夫曼编码
*
* @param bytes
* @param huffCodes2
* @return
*/
private static byte[] zip(byte[] bytes, Map<Byte, String> huffCodes) {
StringBuilder sb = new StringBuilder();
// 把需要压缩的byte数组处理成一个二进制的字符串
for (byte b : bytes) {
sb.append(huffCodes.get(b));
}
// System.out.println(sb.toString());
// 定义长度
int len;
if (sb.length() % 8 == 0) {
len = sb.length() / 8;
} else {
len = sb.length() / 8 + 1;
}
// 用于存储压缩后的byte
byte[] by = new byte[len];
// 记录新byte的位置
int index = 0;
for (int i = 0; i < sb.length(); i += 8) {
String strByte;
if (i + 8 > sb.length()) {
strByte = sb.substring(i);
} else {
strByte = sb.substring(i, i + 8);
}
byte byt = (byte) Integer.parseInt(strByte, 2);//二进制转十进制
System.out.println(strByte + " : " + byt);
by[index] = byt;
index++;
}
return by;
}
/**
* 使用指定的赫夫曼编码表进行解码
*
* @param huffCodes2
* @param b
* @return
*/
private static byte[] decode(Map<Byte, String> huffCodes, byte[] bytes) {
StringBuilder sb = new StringBuilder();
// 把byte数组转为一个二进制的字符串
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
// 是否是最后一个。
boolean flag = (i == bytes.length - 1);
sb.append(byteToBitStr(!flag, b));
}
// 把字符串按照指定的赫夫曼编码进行解码
// 把赫夫曼编码的键值对进行调换
Map<String, Byte> map = new HashMap<>();
for (Map.Entry<Byte, String> entry : huffCodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
// 创建一个集合,用于存byte
List<Byte> list = new ArrayList<>();
// 处理字符串
for (int i = 0; i < sb.length();) {
int count = 1;
boolean flag = true;
Byte b = null;
// 截取出一个byte
while (flag) {
String key = sb.substring(i, i + count);
b = map.get(key);
if (b == null) {
count++;
} else {
flag = false;
}
}
list.add(b);
i += count;
}
// 把集合转为数组
byte[] b = new byte[list.size()];
for (int i = 0; i < b.length; i++) {
b[i] = list.get(i);
}
return b;
}
/**
* 转为8位的字符串
* @param flag
* @param b
* @return
*/
private static String byteToBitStr(boolean flag, byte b) {
int temp = b;
if (flag) {
temp |= 256;//按位或 256
}
String str = Integer.toBinaryString(temp);//返回int变量的二进制表示的字符串。
if (flag) {
return str.substring(str.length() - 8);
} else {
return str;
}
}
}
11111111111111111~