nodes.remove(leftNode);
nodes.remove(rightNode);
// 5)将parent加入到nodes
nodes.add(parent);
}
// 返回赫夫曼树的头结点
return nodes.get(0);
}
}
// 创建节点类
// 为了让Node对象支持排序Collections集合排序
// 让Node实现Comparable接口
class Node implements Comparable {
int value; // 结点权值
Node left; // 指向左子节点
Node right; // 指向右子节点
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return “Node{” +
“value=” + value +
‘}’;
}
@Override
public int compareTo(Node o) {
// 表示从小到大排序
return this.value - o.value;
// 表示从大到小排序
// return -(this.value - o.value);
}
// 前序遍历
public void preOrder() {
System.out.print(this.value + " ");
if (this.left != null) {
this.left.preOrder();
}
if (this.right != null) {
this.right.preOrder();
}
}
}
================================================================
-
赫夫曼编码也翻译为 哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式, 属于一种程序算法
-
赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。
-
赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~90%之间
-
赫夫曼码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,称之为最佳编码
- 通信领域中信息的处理方式1-定长编码
i like like like java do you like a java // 共40个字符(包括空格)
105 32 108 105 107 101 32 108 105 107 101 32 108 105 107 101 32 106 97 118 97 32 100 111 32 121 111 117 32 108 105 107 101 32 97 32 106 97 118 97 //对应Ascii码
01101001 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101010 01100001 01110110 01100001 00100000 01100100 01101111 00100000 01111001 01101111 01110101 00100000 01101100 01101001 01101011 01100101 00100000 01100001 00100000 01101010 01100001 01110110 01100001 //对应的二进制
按照二进制来传递信息,总的长度是 359 (包括空格)
- 通信领域中信息的处理方式2-变长编码
i like like like java do you like a java // 共40个字符(包括空格)
d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9 // 各个字符对应的个数
0= , 1=a, 10=i, 11=e, 100=k, 101=l, 110=o, 111=v, 1000=j, 1001=u, 1010=y, 1011=d
说明:按照各个字符出现的次数进行编码,原则是出现次数越多的,则编码越小,比如 空格出现了9 次, 编码为0 ,其它依次类推.
按照上面给各个字符规定的编码,则我们在传输 “i like like like java do you like a java” 数据时,编码就是 10010110100…
字符的编码都不能是其他字符编码的前缀,符合此要求的编码叫做前缀编码, 即不能匹配到重复的编码
- 通信领域中信息的处理方式3-赫夫曼编码
i like like like java do you like a java // 共40个字符(包括空格)
d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9 // 各个字符对应的个数
按照上面字符出现的次数构建一颗赫夫曼树, 次数作为权值.(图后)
赫夫曼编码实现
分析
图解
赫夫曼编码是无损处理方案
注意
这个赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是wpl 是一样的,都是最小的, 比如: 如果我们让每次生成的新的二叉树总是排在权值相同的二叉树的最后一个,则生成的二叉树为:
最佳实践-数据压缩(创建赫夫曼树)
将给出的一段文本,比如 “i like like like java do you like a java” , 根据前面的讲的赫夫曼编码原理,对其进行数据压缩处理 ,形式如 "1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110
"
-
步骤1:根据赫夫曼编码压缩数据的原理,需要创建 “i like like like java do you like a java” 对应的赫夫曼树.
-
思路:前面已经分析过了,而且我们已然讲过了构建赫夫曼树的具体实现。
代码实现(创建赫夫曼树)
// 通过List创建对应赫夫曼树
/**
-
通过List创建对应赫夫曼树
-
@param nodes 需要创建赫夫曼树的所有节点组成的List
-
@return 赫夫曼树根节点
*/
private static Node createHuffmanTree(List 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移出
nodes.remove(leftNode);
nodes.remove(rightNode);
// 将新的二叉树加入到nodes
nodes.add(parent);
}
// nodes最后剩下的结点就是哈夫曼树的根节点
return nodes.get(0);
}
最佳实践-数据压缩(生成赫夫曼编码和赫夫曼编码后的数据)
我们已经生成了 赫夫曼树, 下面我们继续完成任务
-
生成赫夫曼树对应的赫夫曼编码 , 如下表: =01 a=100 d=11000 u=11001 e=1110 v=11011 i=101 y=11010 j=0010 k=1111 l=000 o=0011
-
使用赫夫曼编码来生成赫夫曼编码数据 ,即按照上面的赫夫曼编码,将"i like like like java do you like a java" 字符串生成对应的编码数据, 形式如下.1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100
-
思路:前面已经分析过了,而且我们讲过了生成赫夫曼编码的具体实现。
代码实现(生成赫夫曼编码和赫夫曼编码后的数据)
// 生成的赫夫曼树对应的赫夫曼编码
// 思路:
// 1. 将赫夫曼编码表存放到一个Map<Byte,String>中
// 2. 在生成赫夫曼编码表时,需要拼接路径,所以创建一个StringBuilder,存储某个叶子节点的路径
static Map<Byte, String> huffmanCodes = new HashMap<Byte, String>();
static StringBuilder stringBuilder = new StringBuilder();
/**
-
功能:将传入的node结点的所有叶子结点的赫夫曼编码得到,并放入到huffmanCodes中
-
@param node 传入结点,默认根节点
-
@param code 路径:左子节点为0;右子节点为1
-
@param stringBuilder 用于拼接路径
*/
private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
StringBuilder stringBuilder1 = new StringBuilder(stringBuilder);
// 将传入的code加入到stringBuilder1
stringBuilder1.append(code);
if (node != null) { // 如果node==null 不处理
// 判断当前node是叶子节点还是非叶子节点
if (node.data == null) {// 非叶子节点
// 递归处理
// 向左递归
getCodes(node.left, “0”, stringBuilder1);
// 向右递归
getCodes(node.right, “1”, stringBuilder1);
} else { // 叶子节点
// 就表示找到某个叶子节点
huffmanCodes.put(node.data, stringBuilder1.toString());
}
}
}
/**
-
编写一个方法,将字符串对应的byte[] 数组,通过生成的赫夫曼编码表,返回一个赫夫曼编码压缩后的byte[]
-
@param bytes 这是原始的字符串对应的byte[]
-
@param huffmanCodes 生成的赫夫曼编码map
-
@return 返回赫夫曼编码处理后的byte[]
-
举例:接收"i like like like java do you like a java"对应的byte[]数组 => byte[] contentBytes = str.getBytes()
-
返回的是1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110
-
该字符串对应的字节数组byte[] huffmanCodeBytes,即8位对应一个byte,放入huffmanCodeBytes
-
huffmanCodeBytes[0] = 10101000(补码) => byte[推导 10101000(补码) => 10101000 - 1 => 10100111(反码) => 11011000(原码) = -88]
-
即使 huffmanCodeByte[1] = -88
*/
public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
// 1. 先利用huffmanCodes 将 bytes 转成 赫夫曼编码对应的字符串
StringBuilder stringBuilder = new StringBuilder();
// 遍历bytes数组
for (byte b : bytes) {
stringBuilder.append(huffmanCodes.get(b));
}
// 将字符串转成byte[] 数组
// 统计返回的huffmanCodes长度
// 一句话 int len = (stringBuilder.length() + 7) / 8
int len;
if (stringBuilder.length() % 8 == 0) {
len = stringBuilder.length() / 8;
} else {
len = stringBuilder.length() / 8 + 1;
}
// 创建存储压缩后的byte数组
byte[] huffmanCondeBytes = new byte[len];
int index = 0; // 记录第几个byte
for (int i = 0; i < stringBuilder.length(); i += 8) { // 因为每八位对应一个byte,所以步长+8
String strByte;
if (i + 8 > stringBuilder.length()) { // 不够八位
strByte = stringBuilder.substring(i);
} else {
strByte = stringBuilder.substring(i, i + 8);
}
// 将strByte转成数组放到
huffmanCondeBytes[index] = (byte) Integer.parseInt(strByte, 2);
index++;
}
return huffmanCondeBytes;
}
最佳实践-数据解压(使用赫夫曼编码解码)
使用赫夫曼编码来解码数据,具体要求是
-
前面我们得到了赫夫曼编码和对应的编码byte[] , 即:[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]
-
现在要求使用赫夫曼编码, 进行解码,又重新得到原来的字符串"i like like like java do you like a java"
-
思路:解码过程,就是编码的一个逆向操作。
代码实现(使用赫夫曼编码解压)
/**
-
将一个byte转成一个二进制字符串
-
@param b 接收的byte
-
@param flag 标识是否需要补高位,如果是true,表示需要补高位,如果是false不补
-
如果是最后一个字节,无须补高位
-
@return b对应的二进制字符串(注意是按照补码返回)
*/
private static String byteToBitString(boolean flag, byte b) {
// 使用变量保存b
int temp = b;
// 如果是正数,需要补高位
if (flag) {
temp |= 256; // 按位与 256 1 0000 0000 | 0000 0001 => 1 0000 0001
}
String str = Integer.toBinaryString(temp); // 返回的是temp对应的二进制的补码
if (flag) {
str = str.substring(str.length() - 8);
}
return str;
}
/**
-
编写一个方法,完成对压缩数据的解码
-
@param huffmanCodes 赫夫曼编码表Map
-
@param huffmanBytes 赫夫曼编码得到的字节数组
-
@return 原来的字符串对应的数组
*/
public static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {
// 1. 先得到huffmanBytes对应的二进制的字符串,形式1010100010111…
StringBuilder stringBuilder = new StringBuilder();
// 将byte数组转成二进制字符串
for (int i = 0; i < huffmanBytes.length; i++) {
// 判断是不是最后一个字节
boolean flag = (i == huffmanBytes.length - 1);
stringBuilder.append(byteToBitString(!flag, huffmanBytes[i]));
}
// System.out.println(stringBuilder.toString());
// 把字符串按照指定的赫夫曼编码进行解码
// 把赫夫曼编码表反向调换 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 list = new ArrayList<>();
// i 可以理解成就是索引,扫描stringBuilder
for (int i = 0; i < stringBuilder.length()😉 {
int count = 1; // 小的计数器
boolean flag = true;
Byte b = null;
while (flag) {
// 递增取出一个’1’ 或者 ‘0’
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; // 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;
}
最佳实践-文件压缩
我们学习了通过赫夫曼编码对一个字符串进行编码和解码, 下面我们来完成对文件的压缩和解压,
-
具体要求:给你一个图片文件,要求对其进行无损压缩, 看看压缩效果如何。
-
思路:读取文件-> 得到赫夫曼编码表 -> 完成压缩
代码实现(文件压缩)
/**
-
将一个文件进行压缩
-
@param srcFile 传入的希望压缩的文件的全路径
-
@param dstFile 压缩后将压缩文件放到哪个目录
*/
public static void zipFile(String srcFile,String dstFile){
// 创建一个文件输入流
FileInputStream is = null;
// 创建输出流
OutputStream os = null;
// 创建对象输出流
ObjectOutputStream oos = null;
try {
is = new FileInputStream(srcFile);
// 创建一个和源文件大小一样的byte数组
byte[] b = new byte[is.available()];
// 读取文件
is.read(b);
// 直接对源文件亚索
byte[] huffmanBytes = huffmanZip(b);
// 创建一个文件输出流,存放压缩文件
os = new FileOutputStream(dstFile);
// 创建一个和文件输出流关联的ObjectOutputStream
oos = new ObjectOutputStream(os);
// 把赫夫曼编码后的字节数组写入压缩文件
oos.writeObject(huffmanBytes); // 我们吧
// 这里我们以对象流的方式写入赫夫曼编码,目的是为了以后解压的时候恢复原文件的时候使用
// 注意一定要把赫夫曼编码写入压缩文件
oos.writeObject(huffmanCodes);
}catch (Exception e){
System.out.println(e.getMessage());
}finally {
try {
is.close();
os.close();
oos.close();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
最佳实践-文件解压(文件恢复)
-
具体要求:将前面压缩的文件,重新恢复成原来的文件。
-
思路:读取压缩文件(数据和赫夫曼编码表)-> 完成解压(文件恢复)
/**
-
编写一个方法,完成对压缩文件的解压
-
@param zipFile 准备解压的文件
-
@param dstFile 将文件解压到哪个路径
*/
public static void unZip(String zipFile,String dstFile){
// 定义文件输入流
InputStream is = null;
// 定义对象输入流
ObjectInputStream ois = null;
// 定义文件输出流
OutputStream os = null;
try {
// 创建文件输入流
is = new FileInputStream(zipFile);
// 创建一个和is关联的对象输入流
ois = new ObjectInputStream(is);
// 读取byte数组 huffmanBytes
byte[] huffmanBytes = (byte[])ois.readObject();
// 读取保存的赫夫曼编码表
Map<Byte,String> huffmanCodes = (Map<Byte, String>)ois.readObject();
// 解码
byte[] bytes = decode(huffmanCodes, huffmanBytes);
// 将bytes数组写入文件
os = new FileOutputStream(dstFile);
// 写数据到dstFile文件
os.write(bytes);
}catch (Exception e){
System.out.println(e.getMessage());
}finally {
try {
is.close();
ois.close();
os.close();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
赫夫曼编码压缩文件注意事项
-
如果文件本身就是经过压缩处理的,那么使用赫夫曼编码再压缩效率不会有明显变化, 比如视频,ppt 等等文件
-
赫夫曼编码是按字节来处理的,因此可以处理所有的文件(二进制文件、文本文件)
-
如果一个文件中的内容,重复的数据不多,压缩效果也不会很明显.
赫夫曼编码代码汇总
package huffmancode;
import java.io.*;
import java.util.*;
import java.util.zip.ZipFile;
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();
byte[] huffmanCodesBytes = huffmanZip(contentBytes);
System.out.println(Arrays.toString(huffmanCodesBytes));
// 解码
byte[] sourceBytes = decode(huffmanCodes, huffmanCodesBytes);
System.out.println(new String(sourceBytes));*/
/* 分布过程
List nodes = getNodes(contentBytes);
// System.out.println(nodes);
// 测试创建的二叉树
// System.out.println(“赫夫曼树:”);
Node huffmanTreeRoot = createHuffmanTree(nodes);
// preOrder(huffmanTreeRoot);
// 测试是否生成对应哈夫曼编码
Map<Byte, String> huffmanCodes = getCodes(huffmanTreeRoot);
byte[] huffmanCodeBytes = zip(contentBytes, huffmanCodes);
System.out.println(Arrays.toString(huffmanCodeBytes));*/
// 测试压缩文件
// String srcFile = “d://src.pdf”;
// String desFile = “d://dst.zip”;
// zipFile(srcFile,desFile);
// System.out.println(“压缩文件成功”);
// 测试解压文件
String zipFile = “d://dst.zip”;
String dstFile = “d://src2.pdf”;
unZip(zipFile,dstFile);
System.out.println(“解压成功”);
}
/**
-
将一个文件进行压缩
-
@param srcFile 传入的希望压缩的文件的全路径
-
@param dstFile 压缩后将压缩文件放到哪个目录
*/
public static void zipFile(String srcFile,String dstFile){
// 创建一个文件输入流
FileInputStream is = null;
// 创建输出流
OutputStream os = null;
// 创建对象输出流
ObjectOutputStream oos = null;
try {
is = new FileInputStream(srcFile);
// 创建一个和源文件大小一样的byte数组
byte[] b = new byte[is.available()];
// 读取文件
is.read(b);
// 直接对源文件亚索
byte[] huffmanBytes = huffmanZip(b);
// 创建一个文件输出流,存放压缩文件
os = new FileOutputStream(dstFile);
// 创建一个和文件输出流关联的ObjectOutputStream
oos = new ObjectOutputStream(os);
// 把赫夫曼编码后的字节数组写入压缩文件
oos.writeObject(huffmanBytes); // 我们吧
// 这里我们以对象流的方式写入赫夫曼编码,目的是为了以后解压的时候恢复原文件的时候使用
// 注意一定要把赫夫曼编码写入压缩文件
oos.writeObject(huffmanCodes);
}catch (Exception e){
System.out.println(e.getMessage());
}finally {
try {
is.close();
os.close();
oos.close();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
/**
-
编写一个方法,完成对压缩文件的解压
-
@param zipFile 准备解压的文件
-
@param dstFile 将文件解压到哪个路径
*/
public static void unZip(String zipFile,String dstFile){
// 定义文件输入流
InputStream is = null;
// 定义对象输入流
ObjectInputStream ois = null;
// 定义文件输出流
OutputStream os = null;
try {
// 创建文件输入流
is = new FileInputStream(zipFile);
// 创建一个和is关联的对象输入流
ois = new ObjectInputStream(is);
// 读取byte数组 huffmanBytes
byte[] huffmanBytes = (byte[])ois.readObject();
// 读取保存的赫夫曼编码表
Map<Byte,String> huffmanCodes = (Map<Byte, String>)ois.readObject();
// 解码
byte[] bytes = decode(huffmanCodes, huffmanBytes);
// 将bytes数组写入文件
os = new FileOutputStream(dstFile);
// 写数据到dstFile文件
os.write(bytes);
}catch (Exception e){
System.out.println(e.getMessage());
}finally {
try {
is.close();
ois.close();
os.close();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
// 完成数据的解压
// 思路:
// 1. 将huffmanCodeBytes[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]转成赫夫曼编码对应的二进制字符串
// 重写先转成赫夫曼编码对应的二进制的字符串"1010100010111…"
// 2. 将赫夫曼编码对应的二进制字符串转成原始字符串
/**
-
编写一个方法,完成对压缩数据的解码
-
@param huffmanCodes 赫夫曼编码表Map
-
@param huffmanBytes 赫夫曼编码得到的字节数组
-
@return 原来的字符串对应的数组
*/
public static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {
// 1. 先得到huffmanBytes对应的二进制的字符串,形式1010100010111…
StringBuilder stringBuilder = new StringBuilder();
// 将byte数组转成二进制字符串
for (int i = 0; i < huffmanBytes.length; i++) {
// 判断是不是最后一个字节
boolean flag = (i == huffmanBytes.length - 1);
stringBuilder.append(byteToBitString(!flag, huffmanBytes[i]));
}
// System.out.println(stringBuilder.toString());
// 把字符串按照指定的赫夫曼编码进行解码
// 把赫夫曼编码表反向调换 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 list = new ArrayList<>();
// i 可以理解成就是索引,扫描stringBuilder
for (int i = 0; i < stringBuilder.length()😉 {
int count = 1; // 小的计数器
boolean flag = true;
Byte b = null;
while (flag) {
// 递增取出一个’1’ 或者 ‘0’
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; // 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 b 接收的byte
-
@param flag 标识是否需要补高位,如果是true,表示需要补高位,如果是false不补
-
如果是最后一个字节,无须补高位
-
@return b对应的二进制字符串(注意是按照补码返回)
*/
private static String byteToBitString(boolean flag, byte b) {
// 使用变量保存b
int temp = b;
// 如果是正数,需要补高位
if (flag) {
temp |= 256; // 按位与 256 1 0000 0000 | 0000 0001 => 1 0000 0001
}
String str = Integer.toBinaryString(temp); // 返回的是temp对应的二进制的补码
if (flag) {
str = str.substring(str.length() - 8);
}
return str;
}
/**
-
使用一个方法,将前面的方法封装起来,便于调用
-
@param bytes 原始的字符串对应的字节数组
-
@return 返回的是经过赫夫曼编码处理后(压缩后)的数组
*/
private static byte[] huffmanZip(byte[] bytes) {
List nodes = getNodes(bytes);
// 根据nodes创建赫夫曼树
Node huffmanTreeRoot = createHuffmanTree(nodes);
// 根据赫夫曼树创建对应的赫夫曼编码
Map<Byte, String> huffmanCodes = getCodes(huffmanTreeRoot);
// 根据赫夫曼编码对原始赫夫曼编码亚索
byte[] huffmanCodeBytes = zip(bytes, huffmanCodes);
return huffmanCodeBytes;
}
/**
-
编写一个方法,将字符串对应的byte[] 数组,通过生成的赫夫曼编码表,返回一个赫夫曼编码压缩后的byte[]
-
@param bytes 这是原始的字符串对应的byte[]
-
@param huffmanCodes 生成的赫夫曼编码map
-
@return 返回赫夫曼编码处理后的byte[]
-
举例:接收"i like like like java do you like a java"对应的byte[]数组 => byte[] contentBytes = str.getBytes()
-
返回的是1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110
-
该字符串对应的字节数组byte[] huffmanCodeBytes,即8位对应一个byte,放入huffmanCodeBytes
-
huffmanCodeBytes[0] = 10101000(补码) => byte[推导 10101000(补码) => 10101000 - 1 => 10100111(反码) => 11011000(原码) = -88]
-
即使 huffmanCodeByte[1] = -88
*/
public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
// 1. 先利用huffmanCodes 将 bytes 转成 赫夫曼编码对应的字符串
StringBuilder stringBuilder = new StringBuilder();
// 遍历bytes数组
for (byte b : bytes) {
stringBuilder.append(huffmanCodes.get(b));
}
// 将字符串转成byte[] 数组
// 统计返回的huffmanCodes长度
// 一句话 int len = (stringBuilder.length() + 7) / 8
int len;
if (stringBuilder.length() % 8 == 0) {
len = stringBuilder.length() / 8;
} else {
len = stringBuilder.length() / 8 + 1;
}
// 创建存储压缩后的byte数组
byte[] huffmanCondeBytes = new byte[len];
int index = 0; // 记录第几个byte
for (int i = 0; i < stringBuilder.length(); i += 8) { // 因为每八位对应一个byte,所以步长+8
String strByte;
if (i + 8 > stringBuilder.length()) { // 不够八位
strByte = stringBuilder.substring(i);
} else {
strByte = stringBuilder.substring(i, i + 8);
}
// 将strByte转成数组放到
huffmanCondeBytes[index] = (byte) Integer.parseInt(strByte, 2);
index++;
}
return huffmanCondeBytes;
}
// 为了调用方便,我们重载getNodes
private static Map<Byte, String> getCodes(Node root) {
if (root == null) {
return null;
} else {
// 处理root左子树
getCodes(root.left, “0”, stringBuilder);
getCodes(root.right, “1”, stringBuilder);
}
return huffmanCodes;
}
/**
-
@param bytes 接收一个字节数组
-
@return 返回一个List 形式[Node{date=97,weight=5},Node{date=32,weight=9}…]
*/
private static List getNodes(byte[] bytes) {
// 1. 创建一个ArrayList
ArrayList nodes = new ArrayList<>();
// 2. 遍历bytes,统计每个byte出现的次数 => map
Map<Byte, Integer> counts = new HashMap<>();
for (byte b : bytes) {
// Map仍然没有该字符数据
counts.merge(b, 1, Integer::sum);
}
// 把每一个键值对转换为Node对象,加入nodes集合
for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
nodes.add(new Node(entry.getKey(), entry.getValue()));
}
return nodes;
}
/**
-
通过List创建对应赫夫曼树
-
@param nodes 需要创建赫夫曼树的所有节点组成的List
-
@return 赫夫曼树根节点
*/
private static Node createHuffmanTree(List 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移出
nodes.remove(leftNode);
nodes.remove(rightNode);
// 将新的二叉树加入到nodes
nodes.add(parent);
}
// nodes最后剩下的结点就是哈夫曼树的根节点
return nodes.get(0);
}
// 生成的赫夫曼树对应的赫夫曼编码
// 思路:
// 1. 将赫夫曼编码表存放到一个Map<Byte,String>中
// 2. 在生成赫夫曼编码表时,需要拼接路径,所以创建一个StringBuilder,存储某个叶子节点的路径
static Map<Byte, String> huffmanCodes = new HashMap<Byte, String>();
static StringBuilder stringBuilder = new StringBuilder();
/**
-
功能:将传入的node结点的所有叶子结点的赫夫曼编码得到,并放入到huffmanCodes中
-
@param node 传入结点,默认根节点
-
@param code 路径:左子节点为0;右子节点为1
-
@param stringBuilder 用于拼接路径
*/
private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
StringBuilder stringBuilder1 = new StringBuilder(stringBuilder);
// 将传入的code加入到stringBuilder1
stringBuilder1.append(code);
if (node != null) { // 如果node==null 不处理
// 判断当前node是叶子节点还是非叶子节点
if (node.data == null) {// 非叶子节点
// 递归处理
// 向左递归
getCodes(node.left, “0”, stringBuilder1);
// 向右递归
getCodes(node.right, “1”, stringBuilder1);
} else { // 叶子节点
// 就表示找到某个叶子节点
huffmanCodes.put(node.data, stringBuilder1.toString());
}
}
}
// 前序遍历方法
private static void preOrder(Node root) {
if (root != null) {
root.preOrder();
} else {
System.out.println(“该树为空!”);
}
}
}
class Node implements Comparable {
Byte data; // 存放数据(字符)本身,比如’a’ => 97, ’ ’ => 32
int weight; // 权值,表示字符出现次数
Node left;
Node right;
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 this.weight - o.weight;
}
// 前序遍历
public void preOrder() {
System.out.print(this.data + “:” + this.weight + " ");
if (this.left != null) {
this.left.preOrder();
}
if (this.right != null) {
this.right.preOrder();
}
}
}
================================================================
给你一个数列 (7, 3, 10, 12, 5, 1, 9),要求能够高效的完成对数据的查询和添加。
- 使用数组
数组未排序, 优点:直接在数组尾添加,速度快。 缺点:查找速度慢.
数组排序,优点:可以使用二分查找,查找速度快,缺点:为了保证数组有序,在添加新数据时,找到插入位置后,后面的数据需整体移动,速度慢。
-
使用链式存储-链表不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体移动。
-
使用二叉排序树
二叉排序树:BST: (Binary Sort(Search) Tree), 对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点
比如针对前面的数据 (7, 3, 10, 12, 5, 1, 9) ,对应的二叉排序树为:
一个数组创建成对应的二叉排序树,并使用中序遍历二叉排序树,比如: 数组为 Array(7, 3, 10, 12, 5, 1, 9) , 创建成对应的二叉排序树为 :
代码
package binarysourtree;
public class BinarySortTreeDemo {
public static void main(String[] args) {
int[] arr = {7, 3, 10, 12, 5, 1, 9};
BinarySortTree binarySortTree = new BinarySortTree();
for(int a:arr){
binarySortTree.add(new Node(a));
}
binarySortTree.infixOrder();
}
}
// 创建二叉排序树
class BinarySortTree {
private Node root;
// 添加节点的方法
public void add(Node node) {
if (root == null) { // 如果root为空,直接把node加上
root = node;
} else {
root.add(node);
}
}
// 中序遍历
public void infixOrder() {
if (root != null) {
root.infixOrder();
} else {
System.out.println(“二叉排序树为空”);
}
}
}
// 创建节点
class Node {
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
// 添加节点方法
// 递归形式,需要满足二叉排序树
public void add(Node node) {
if (node == null) {
return;
}
// 判断传入节点的值和当前子树根节点值的关系
if (node.value < this.value) {
// 如果当前结点左子节点为null
if (this.left == null) {
this.left = node;
} else {
// 递归向左子树添加
this.left.add(node);
}
} else { // 添加的节点的值大于等于当前结点的值
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
最后
码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到
又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考
以下是部分内容截图
log.csdnimg.cn/20201008092124652.png#pic_center)
代码
package binarysourtree;
public class BinarySortTreeDemo {
public static void main(String[] args) {
int[] arr = {7, 3, 10, 12, 5, 1, 9};
BinarySortTree binarySortTree = new BinarySortTree();
for(int a:arr){
binarySortTree.add(new Node(a));
}
binarySortTree.infixOrder();
}
}
// 创建二叉排序树
class BinarySortTree {
private Node root;
// 添加节点的方法
public void add(Node node) {
if (root == null) { // 如果root为空,直接把node加上
root = node;
} else {
root.add(node);
}
}
// 中序遍历
public void infixOrder() {
if (root != null) {
root.infixOrder();
} else {
System.out.println(“二叉排序树为空”);
}
}
}
// 创建节点
class Node {
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
// 添加节点方法
// 递归形式,需要满足二叉排序树
public void add(Node node) {
if (node == null) {
return;
}
// 判断传入节点的值和当前子树根节点值的关系
if (node.value < this.value) {
// 如果当前结点左子节点为null
if (this.left == null) {
this.left = node;
} else {
// 递归向左子树添加
this.left.add(node);
}
} else { // 添加的节点的值大于等于当前结点的值
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-554LsfJE-1710971845575)]
[外链图片转存中…(img-ewiXzzya-1710971845575)]
[外链图片转存中…(img-MSKCxyQq-1710971845576)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-YPLf6at0-1710971845576)]
最后
码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到
又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考
以下是部分内容截图
[外链图片转存中…(img-jUtR6yeW-1710971845577)]