数据结构和算法学习笔记五_树结构的实际应用(1)
学习视频:尚硅谷韩老师Java讲解数据结构与算法
一、 堆排序
1.1、 堆排序基本介绍
- 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复
杂度均为 O(nlogn),它也是不稳定排序。
- 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆, 注意 : 没有
要求结点的左孩子的值和右孩子的值的大小关系。
-
每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
-
大顶堆举例说明:
- 小顶堆举例说明 :
- 一般升序采用大顶堆,降序采用小顶堆
1.2、堆排序基本思想
-
将待排序序列构造成一个大顶堆
-
此时,整个序列的最大值就是堆顶的根节点。
-
将其与末尾元素进行交换,此时末尾就为最大值。
-
然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n 个元素的次小值。如此反复执行,便能得到一个有序
序列了。
1.3、代码:
package com.lxf.Tree;
import java.util.Arrays;
import java.util.Random;
public class HeapSort {
public static void main(String[] args) {
//获取一个数字字符串
StringBuilder stringBuilder = new StringBuilder();
Random random = new Random();
for (int i = 0; i < 100000; i++) {
stringBuilder.append(random.nextInt(500)+",");
}
//去掉最后一个,
stringBuilder.setLength(stringBuilder.length()-1);
//获取整数数组
String[] splits= stringBuilder.toString().split(",");
int[] nums = Arrays.stream(splits).mapToInt(Integer::valueOf).toArray();
//获取开始时间
long start = System.currentTimeMillis();
System.out.println("start = " + start);
//堆排序排序算法
heapSort(nums);
//获取执行完的时间
long end = System.currentTimeMillis();
System.out.println("end = " + end);
//计算执行时间
long sub=end-start;
System.out.println("花费时间="+sub);
//三次测试:27.218秒 24.579秒 27.254秒
}
/**
* 堆排序
*
* @param arr
*/
public static void heapSort(int arr[]) {
for (int i = arr.length - 1; i > 0; i--) {
max_heapify(arr, i);
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
}
}
/**
* 构造大根堆
*
* @param arr 数组
* @param n 当前构造大根堆数组的长度
* 注意:这个树必须是完全二叉树
*/
private static void max_heapify(int arr[], int n) {
for (int j = (n - 1) / 2; j >= 0; j--) {
int children = 2 * j + 1;
if (children != n && arr[children] < arr[children + 1]) {
children++;
}
if (arr[j] < arr[children]) {
int temp = arr[j];
arr[j] = arr[children];
arr[children] = temp;
}
}
}
}
二、赫夫曼树(哈夫曼树)
2.1、基本介绍
- 给定 n 个权值作为 n 个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为
最优二叉树,也称为哈夫曼树(Huffman Tree), 还有的书翻译为霍夫曼树。
- 赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近
2.2、赫夫曼树几个重要概念和举例说明
- 路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路
中分支的数目称为路径长度。若规定根结点的层数为 1,则从根结点到第 L 层结点的路径长度为 L-1
- 结点的权及带权路径长度:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结
点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积
- 树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为 WPL(weighted path
length) ,权值越大的结点离根结点越近的二叉树才是最优二叉树。
- WPL 最小的就是赫夫曼树
2.3、赫夫曼树创建思路图解
问题:给你一个数列 {13, 7, 8, 3, 29, 6, 1},要求转成一颗赫夫曼树.
构成赫夫曼树的步骤:
-
从小到大进行排序, 将每一个数据,每个数据都是一个节点 , 每个节点可以看成是一颗最简单的二叉树
-
取出根节点权值最小的两颗二叉树
-
组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
-
再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数
据都被处理,就得到一颗赫夫曼树
- 图解:
package com.lxf.huffmanTree;
import java.util.PriorityQueue;
public class HuffmanTreeDemo{
public static void main(String[] args) {
int[] arr={
13,7,8,3,29,6,1};
//创建哈夫曼树
Node root = createHuffmanTree(arr);
//前序遍历打印哈夫曼树
//预期:67、29、38、15、7、8、23、10、4、1、3、6、13
HuffmanTree huffmanTree = new HuffmanTree(root);
huffmanTree.preOrder();
//结果:67 29 38 15 7 8 23 10 4 1 3 6 13
}
/**
* 创建赫夫曼树的方法
* @param arr
*/
public static Node createHuffmanTree(int[] arr){
//为了操作方便
//1.遍历arr数组
//2.将arr的每个元素构成一个Node
//3.将Node放入PriorityQueue中(已实现Comparable接口,所以nodes是有序的)
PriorityQueue<Node> nodes = new PriorityQueue<Node>();
for (int value : arr) {
nodes.offer(new Node(value));
}
//从nodes中反复取出最小的两个节点拼接成一个子树(子树的节点值为最小的两个节点和)
while(nodes.size()!=1){
//获取第一个节点
Node left = nodes.poll();
//获取第二个节点
Node right = nodes.poll();
//拼接节点
Node parent=new Node(left.value+right.value);
parent.left=left;
parent.right=right;
//再将节点加入
nodes.add(parent);
}
return nodes.peek();
}
}
/**
* 定义BinaryTree二叉树
*/
class HuffmanTree {
private Node root;
public HuffmanTree() {
}
public HuffmanTree(Node root) {
this.root = root;
}
//前序遍历
public void preOrder(){
if(this.root!=null){
this.root.preOrder();
}else{
System.out.println("二叉树为空,无法遍历");
}
}
}
/**
* 创建结点类
* 为了让Node对象持续排序Collection集合排序
* 让Node实现Comparable接口
*/
class Node implements Comparable<Node>{
int value;//结点权值
Node left;//指向左子结点
Node right;//指向右子结点
public Node(int value){
this.value=value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
/**
* 前序遍历的方法
*/
public void preOrder(){
System.out.print(this.value+" ");//先输出父节点的值
//递归向左子树前序遍历
if(this.left!=null){
this.left.preOrder();
}
//递归向右子树前序遍历
if(this.right!=null){
this.right.preOrder();
}
}
@Override
public int compareTo(Node o) {
return Integer.compare(this.value,o.value);
}
}
三、赫夫曼编码
3.1 基本介绍
- 赫夫曼编码也翻译为
哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式, 属于一种程序算法
-
赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。
-
赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在 **20%~90%**之间
-
赫夫曼码是可变字长编码(VLC)的一种。Huffman 于 1952 年提出一种编码方法,称之为最佳编码
3.2 原理剖析
- 通信领域中信息的处理方式 一-定长编码:
- 通信领域中信息的处理方式 二-变长编码
- 通信领域中信息的处理方式 三-赫夫曼编码
传输的 字符串
-
i like like like java do you like a java
-
d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 (空格):9 // 各个字符对应的个数
-
按照上面字符出现的次数构建一颗赫夫曼树, 次数作为权值
- 构成赫夫曼树的步骤:
-
从小到大进行排序, 将每一个数据,每个数据都是一个节点 , 每个节点可以看成是一颗最简单的二叉树
-
取出根节点权值最小的两颗二叉树
-
组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
-
再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数据都被处理,
就得到一颗赫夫曼树
- 根据赫夫曼树,给各个字符,规定编码 (前缀编码), 向左的路径为 0 向右的路径为 1 , 编码
如下:
o: 1000 u: 10010 d: 100110 y: 100111 i: 101 a : 110
k: 1110 e: 1111 j: 0000 v: 0001 l: 001 (空格): 01
- 按照上面的赫夫曼编码,我们的"i like like like java do you like a java"
字符串对应的编码为 (注意这里我们使用的无损压缩)
10101001101111011110100110111101111010011011110111101000011000011100110011110000110
01111000100100100110111101111011100100001100001110 通过赫夫曼编码处理 长度为 133
6) 长度为 : 133
说明:
原来长度是 359 , 压缩了 (359-133) / 359 = 62.9%
此编码满足前缀编码, 即字符的编码都不能是其他字符编码的前缀。不会造成匹配的多义性
赫夫曼编码是无损处理方案
注意事项:
注意, 这个赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是 wpl 是
一样的,都是最小的, 最后生成的赫夫曼编码的长度是一样,比如: 如果我们让每次生成的新的二叉树总是排在权
值相同的二叉树的最后一个,则生成的二叉树为:
3.3、最佳实践-数据压缩与解压(创建赫夫曼树)
将给出的一段文本,比如 “i like like like java do you like a java” , 根据前面的讲的赫夫曼编码原理,对其进行数
据 压 缩 处 理 , 形 式 如:
“1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100 110111101111011100100001100001110”
步骤 1:根据赫夫曼编码压缩数据的原理,需要创建 “i like like like java do you like a java” 对应的赫夫曼树.
思路:
(1)、Node(data(存放数据)、weight(权值)、left和right)
(2)、得到"i like like like java do you like a java"对应的byte[]数组
(3)、编写一个方法,将准备构建赫夫曼树的Node结点放到List,形式[Node[data=97,weight=5],Node[data=32,weight=9]…],体现:d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 (空格):9
(4)、可以通过List创建对应的赫夫曼树
(5)、生成赫夫曼树对应的赫夫曼编码,如下表:
o: 1000 u: 10010 d: 100110 y: 100111 i: 101 a : 110
k: 1110 e: 1111 j: 0000 v: 0001 l: 001 (空格): 01
(6)、使用赫夫曼编码来生成赫夫曼编码数据,即将按照上面的赫夫曼编码,将"i like like like java do you like a java"字符串生成对应的编码数据,形式如下.
1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100
(7)、按原路返回就是解压了
(8)、拓展:压缩与解压文件
(9)、注意:老师在解压过程中忽略了最后不满足8位会丢失0的情况:
- 比如最后几位剩01000,1前面的0就会丢失,造成解压失败(最后截取的时候就会超出范围)
- 比如最后几位剩1000,就不会丢失
- 总结:最后剩余1开头就不会发生丢失情况,剩余0开头就会发生丢失情况
- 所以在测试解压文件时是有概率失败的
- 解决方法:记录缺失的0的数量,在decode的时候补上去就好了
代码实现:
package com.lxf.huffmanTree;
import java.io.*;
import java.util.*;
public class HuffmanCode {
public static void main(String[] args) {
//String str = "i like like like java do you like a java";
//byte[] bytes = str.getBytes();
//System.out.println(bytes.length);//40
//System.out.println(Arrays.toString(bytes));
//byte[] huffmanZip = huffmanZip(bytes);
//System.out.println("压缩后的结果是:"+Arrays.toString(huffmanZip)+",长度="+huffmanZip.length+",压缩率="+(new DecimalFormat("#0.00%").format((double)huffmanZip.length/bytes.length)) );
//byte[] decode = decode(huffmanCodes, huffmanZip);
//System.out.println(Arrays.toString(decode));
//测试压缩文件
zipFile("C:\\Users\\Administrator\\Desktop\\数据结构和算法笔记\\3.png", "C:\\Users\\Administrator\\Desktop\\数据结构和算法笔记\\3.zip");
//测试解压文件
unZipFile("C:\\Users\\Administrator\\Desktop\\数据结构和算法笔记\\3.zip", "C:\\Users\\Administrator\\Desktop\\数据结构和算法笔记\\3copy.png");
}
//统计丢失0的个数
private static Integer countZero;
/**
* 将文件压缩的方法
*
* @param srcFile srcFile 你传入的希望压缩的文件的全路径
* @param desFile dstFile 我们压缩后将压缩文件放到哪个目录
*/
public static void zipFile(String srcFile, String desFile) {
//文件输入流
FileInputStream in = null;
//文件输出流
FileOutputStream out = null;
ObjectOutputStream oos = null;
try {
//创建srcFile文件输入流
in = new FileInputStream(srcFile);
//创建一个和源文件大小一样的byte[]
byte[] b = new byte[in.available()];
//读取整个文件
in.read(b);
//直接对源文件压缩
byte[] huffmanBytes = huffmanZip(b);
//创建desFile文件输入流
oos = new ObjectOutputStream(new FileOutputStream(desFile));
//把赫夫曼编码后的字节数组写入压缩文件
oos.writeObject(huffmanBytes);
//以对象流的方式写入赫夫曼编码(为了以后我们恢复源文件时使用)
oos.writeObject(huffmanCodes);
System.out.println("压缩成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 将文件解压的方法
*
* @param zipFile zipFile 你传入压缩的文件的全路径
* @param desFile dstFile 我们解压后将解压文件放到哪个目录
*/
public static void unZipFile(String zipFile, String desFile) {
//定义文件输入流
FileInputStream in = null;
//定义一个对象输入流
ObjectInputStream ins = null;
//定义文件的输出流
FileOutputStream os = null;
try {
//创建文件输入流
in = new FileInputStream(zipFile);
//创建一个和is关联的对象输入流
ins = new ObjectInputStream(in);
//读取byte数组 huffmanBytes
byte[] huffmanBytes = (byte[]) ins.readObject();
//读取赫夫曼编码表
Map huffmanCodes = (Map<Byte, String>) ins.readObject();
//解码
byte[] bytes = decode(huffmanCodes, huffmanBytes);
//将bytes数组写入到目标文件
os=new FileOutputStream(desFile);
//写数组到desFile目标文件中
os.write(bytes);
System.out.println("解压成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
os.close();
ins.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 完成对压缩数据的解码
*
* @param huffmanCodes 赫夫曼编码表
* @param huffmanBytes 赫夫曼编码得到的字节数组
* @return 就是原来的字符串对应的数组
*/
private static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {
//1.先得到huffmanBytes对应的二进制字符串,形式101010001011...
StringBuilder builder = new StringBuilder();
//将byte数组转成二进制的字符串
for (int i = 0; i < huffmanBytes.length; i++) {
//判断是不是最后一个字节
boolean flag = (i == huffmanBytes.length - 1);
builder.append(byteToBitString(!flag, huffmanBytes[i]));
}
//把字符串安装到指定的赫夫曼编码进行编码
//把赫夫曼编码编码表进行调换,因为反向查询a->100 100->a
HashMap<String, Byte> map = new HashMap<>();
for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
//创建一个集合,存放byte
List<Byte> list = new ArrayList<>();
//遍历builder字符串,找到map(key->二进制编码,value->数值)
for (int i = 0; i < builder.length(); ) {
int count = 1;
boolean flag = true;
Byte b = null;
while (flag) {
//取出一个'1' '0'
String key = builder.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中就存放了所有的字符"i like like like java do you like a java"
//把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,表示需要补高位,如果是false表示不补,如果是最后一个字节,无需补高位
* @param b 转入的byte
* @return 是该b对应的二进制的字符串(注意是按补码返回)
*/
private static String byteToBitString(boolean flag, byte b) {
//使用变量保存b
int temp = b;//将b转成int
//如果是正数我们还存在补高位
if (flag) {
temp |= 256;//按位与256 1 0000 0000 | 0000 0001=>1 0000 0001
}
String str = Integer.toBinaryString(temp);//返回的是temp对应的二进制的补码
if (flag) {
return str.substring(str.length() - 8);
} else {
while(countZero>0){
str="0"+str;
countZero--;
}
return str;
}
}
/**
* @param bytes 原始的字符串对应的字节数组
* @return 经过赫夫曼编码处理后的字节数组
*/
private static byte[] huffmanZip(byte[] bytes) {
//1.统计每个字符对应的数量并获取每个字符对应的结点
List<Node> nodes = getNodes(bytes);
//System.out.println(nodes);
//[Node [data= 32 weight=9], Node [data= 97 weight=5], Node [data= 100 weight=1], Node [data= 101 weight=4], Node [data= 117 weight=1], Node [data= 118 weight=2], Node [data= 105 weight=5], Node [data= 121 weight=1], Node [data= 106 weight=2], Node [data= 107 weight=4], Node [data= 108 weight=4], Node [data= 111 weight=2]]
//2.创建赫夫曼结点树
Node huffmanTree = createHuffmanTree(nodes);
//System.out.println("前序遍历:");
//preOrder(huffmanTree);
// Node [data= null weight=40]
// Node [data= null weight=17]
// Node [data= null weight=8]
// Node [data= 108 weight=4]
// Node [data= null weight=4]
// Node [data= 106 weight=2]
// Node [data= 111 weight=2]
// Node [data= 32 weight=9]
// Node [data= null weight=23]
// Node [data= null weight=10]
// Node [data= 97 weight=5]
// Node [data= 105 weight=5]
// Node [data= null weight=13]
// Node [data= null weight=5]
// Node [data= null weight=2]
// Node [data= 100 weight=1]
// Node [data= 117 weight=1]
// Node [data= null weight=3]
// Node [data= 121 weight=1]
// Node [data= 118 weight=2]
// Node [data= null weight=8]
// Node [data= 101 weight=4]
// Node [data= 107 weight=4]
//3.生成的赫夫曼编码表
StringBuilder stringBuilder1 = new StringBuilder();
getCodes(huffmanTree, "", stringBuilder1);
// getCodes(huffmanTree);
//System.out.println("生成的赫夫曼编码表:"+huffmanCodes);
//生成的赫夫曼编码表:{32=01, 97=100, 100=11000, 117=11001, 101=1110, 118=11011, 105=101, 121=11010, 106=0010, 107=1111, 108=000, 111=0011}
//4.生成赫夫曼编码对应的字节数组
StringBuilder stringBuilder2 = new StringBuilder();
byte[] huffmanCodeBytes = zip(bytes, huffmanCodes, stringBuilder2);
//System.out.println("huffmanCodeBytes="+Arrays.toString(huffmanCodeBytes));
//huffmanCodeBytes=[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]
//压缩完毕,可以发送了~
return huffmanCodeBytes;
}
/**
* 将字符串对应的byte[] 数组,通过生成的赫夫曼编码表,返回一个赫夫曼编码 压缩后的byte[]
*
* @param bytes 这时原始的字符串对应的byte[]
* @param huffmanCodes 生成的赫夫曼编码map
* @return 返回赫夫曼编码处理后一个数组
* <p>
* 举例:String str="i like like like java do you like a java";
* byte[] bytes = str.getBytes();
* 返回:10101000101111111100100010111111110010001011111111001001010011011100011100000110111010001111001010 00101111111100110001001010011011100
* 对应的byte[] huffmanCodeBytes 即8位对应一个byte,放入到huffmanCodeBytes
* huffmanCodeBytes[0]=1010100(补码) =>byte [10101000 - 1=>10100111(反码)=>11011000(原码)]
* huffmanCodeBytes[1]=-88
*/
private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes, StringBuilder stringBuilder) {
//1.利用huffmanCodes将bytes转成赫夫曼编码对应的字符串
StringBuilder builder = new StringBuilder();
//遍历byte 数组
for (byte b : bytes) {
stringBuilder.append(huffmanCodes.get(b));
}
//System.out.println(stringBuilder.toString());
//统计返回byte[] huffmanCodeBytes长度
int len = (stringBuilder.length() + 7) / 8;
//创建一个压缩后的byte数组
byte[] huffmanCodeBytes = new byte[len];
//将stringBuilder每8位截一次
int index = 0;
countZero=0;
for (int i = 0; i < stringBuilder.length(); i += 8) {
String strByte;
if (i + 8 > stringBuilder.length()) {
strByte = stringBuilder.substring(i);
System.out.println(strByte);
int j=i;
while(j<stringBuilder.length()-1&&'0'==stringBuilder.charAt(j)){
countZero++;
j++;
}
} else {
strByte = stringBuilder.substring(i, i + 8);
}
//将strByte 转成一个byte,放入到
huffmanCodeBytes[index++] = (byte) Integer.parseInt(strByte, 2);
}
return huffmanCodeBytes;
}
//生成赫夫曼树对应的赫夫曼编码
//思路:
//1.将赫夫曼编码表存放在Map<Byte,String>形式
//32->01 97->100 100->11000等等形式
static Map<Byte, String> huffmanCodes = new HashMap<Byte, String>();
//2.在生成赫夫曼编码表示,需要去拼凑路径,定义一个StringBuilder存储某个叶子结点的路径
//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 stringBuilder2 = new StringBuilder(stringBuilder);
//将code加入到stringBuilder2
stringBuilder2.append(code);
if (node != null) {
//如果node==null不处理
//判断当前node,是叶子结点还是非叶子结点
if (node.data == null) {
//非叶子结点
//递归处理
//向左递归
getCodes(node.left, "0", stringBuilder2);
//向右递归
getCodes(node.right, "1", stringBuilder2);
} else {
//说明是一个叶子结点
//表示找到了某个叶子结点的最后
huffmanCodes.put(node.data, stringBuilder2.toString());
}
}
}
/**
* 重载:为了调用方便
*
* @param root
* @return
*/
private static Map<Byte, String> getCodes(Node root, StringBuilder stringBuilder) {
if (root == null) {
return null;
}
//处理root的左子树
getCodes(root.left, "0", stringBuilder);
//处理root的右子树
getCodes(root.right, "1", stringBuilder);
return huffmanCodes;
}
/**
* 前序遍历
*
* @param root
*/
private static void preOrder(Node root) {
if (root != null) {
root.preOrder();
} else {
System.out.println("赫夫曼树为空!");
}
}
/**
* 生成赫夫曼树
*
* @param nodes 传入的结点数组
* @return
*/
private static Node createHuffmanTree(List<Node> nodes) {
while (nodes.size() > 1) {
Collections.sort(nodes);
Node leftNode = nodes.get(0);
Node rightNode = nodes.get(1);
Node parent = new Node(null, leftNode.weight + rightNode.weight);
parent.left = leftNode;
parent.right = rightNode;
nodes.remove(0);
nodes.remove(0);
nodes.add(parent);
}
return nodes.get(0);
}
/**
* @param bytes 接受字节数组
* @return 返回就是List<Node>
*/
private static List<Node> getNodes(byte[] bytes) {
//1.创建一个ArrayList
List<Node> nodes = new ArrayList<>();
//遍历bytes,统计每一个byte出现的次数->map[key,value]
HashMap<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集合中
//遍历map
for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
nodes.add(new Node(entry.getKey(), entry.getValue()));
}
return nodes;
}
}
class Node implements Comparable<Node> {
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 int compareTo(Node o) {
return Integer.compare(this.weight, o.weight);
}
@Override
public String toString() {
return "Node [data= " + data + " weight=" + weight + "]";
}
//前序遍历
public void preOrder() {
System.out.println(this);
if (this.left != null) {
this.left.preOrder();
}
if (this.right != null) {
this.right.preOrder();
}
}
}