树
为什么需要有树
树提高了数据存储,读取的效率。利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的 插入,删除,修改的速度。
二叉树的遍历
二叉树的前序遍历;中序遍历;后续遍历是根据根节点的顺序进行遍历的
前序遍历
public void preOrder(){//二叉树的遍历方法
if(this.root != null){
this.root.preOrder();//返回节点的遍历方法
}else{
System.out.println("二叉树为空,无法遍历");
}
}
节点的遍历方法
public void preOrder(){//前序遍历
System.out.println(this);//先输出父节点
//递归想左子树前序遍历
if(this.left != null){
this.left.preOrder();
}
//递归向右子树前序遍历
if(this.right != null){
this.right.preOrder();
}
}
删除节点的方法
//递归删除节点,叶子节点直接删除,非叶子节点删除子树
public void delNode(int no){
//如果左节点非空且为删除节点,则可以直接删除
if(this.left != null && this.left.no == no){
this.left = null;
return;
}
if(this.right != null && this.right.no == no) {
this.right = null;
return;
}
//对左子树进行递归删除
if(this.left != null){
this.left.delNode(no);
}
if(this.right != null){
this.right.delNode(no);
}
}
顺序存储二叉树
即为将二叉树存储为数组的形式。
注意以下特点:
1)顺序二叉树通常只考虑完全二叉树
2) 第 n 个元素的左子节点为 2 * n + 1
3) 第 n 个元素的右子节点为 2 * n + 2
4) 第 n 个元素的父节点为 (n-1) / 2
5) n : 表示二叉树中的第几个元素(按 0 开始编号如图所示)
遍历方法
public void preOrder(int index) {
//如果数组为空,或者 arr.length = 0
if(arr == null || arr.length == 0) {
System.out.println("数组为空,不能按照二叉树的前序遍历");
}
//输出当前这个元素
System.out.println(arr[index]);
//向左递归遍历
if((index * 2 + 1) < arr.length) {
preOrder(2 * index + 1 );
}
//向右递归遍历
if((index * 2 + 2) < arr.length) {
preOrder(2 * index + 2);
}
}
赫夫曼树
给定 n 个权值作为 n 个叶子结点,构造一棵二叉树, 若该树的带权路径长度(wpl) 达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树。
代码说明
package yasuo;
import com.sun.corba.se.impl.resolver.SplitLocalResolverImpl;
import java.io.*;
import java.util.*;
public class HuffmanCode {
public static void main(String[] args) {
//测试压缩文件
String srcFile = "F:\\1.png";
String dstFile = "F:\\1.zip";
zipFile(srcFile,dstFile);
System.out.println("压缩成果!");
//测试解压文件
String zipFile = "F:\\1.zip";
String sFile = "F:\\2.png";
unZipFile(zipFile,sFile);
System.out.println("解压成功!");
}
//赫夫曼树进行文件的压缩方法
/**
*
* @param srcfile 输入文件路径
* @param dstFile 输出文件路径
*/
public static void zipFile(String srcfile, String dstFile){
//创建输出流
OutputStream os = null;
ObjectOutputStream oos = null;
//创建输入流
FileInputStream is = null;
//为何要放入对象流?因为这里传递的还有配置文件,恢复文件时要用到
try {
is = new FileInputStream(srcfile);
//将文件流转换为数组
byte[] bytes = new byte[is.available()];//该方法可以读取文件中字节的数目
is.read(bytes);
//对文件进行压缩
byte[] bytes1 = huffmanZip(bytes);
//输出
os = new FileOutputStream(dstFile);
oos = new ObjectOutputStream(os);
oos.writeObject(bytes1);
oos.writeObject(huffmanCodes);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
os.close();
} catch (IOException e) {
throw new RuntimeException();//抛出运行异常
}
}
}
//文件的解压
/**
*
* @param zipFile 准备解压文件
* @param dstFile 解压文件路径
*/
public static void unZipFile(String zipFile,String dstFile){
InputStream is = null;
//定义对象输入流
ObjectInputStream ois = null;
//定义对象输出流(为啥要用对象流?)
OutputStream os = null;
try {
is = new FileInputStream(zipFile);
ois = new ObjectInputStream(is);
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 (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
os.close();
ois.close();
is.close();
} catch (Exception e2) {
// TODO: handle exception
System.out.println(e2.getMessage());
}
}
}
//读取文件,解压操作,复习时思考优化一下补0问题。
private 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++) {
byte b = huffmanBytes[i];
//判断是不是最后一个字节
boolean flag = (i == huffmanBytes.length - 1);
stringBuilder.append(byteToBitString(!flag, b));
}
//把字符串安装指定的赫夫曼编码进行解码
//把赫夫曼编码表进行调换,因为反向查询 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());
}
List<Byte> list = new ArrayList <>();
//i 可以理解成就是索引,扫描 stringBuilder
for (int i = 0; i < stringBuilder.length(); ) {
int count = 1; // 小的计数器
boolean flag = true;
Byte b = null;
while (flag) {
//1010100010111...
//递增的取出 key 1
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
}
byte b[] = new byte[list.size()];
for (int i = 0; i < b.length; i++) {
b[i] = list.get(i);
}
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 {
return str;
}
}
//生成huffman树的总方法,byte也是占据1个存储位的字节数据类型
/**
*
* @param bytes 原始字符串的字节数组
* @return 经过赫夫曼处理后的字节数组
* 处理逻辑:先根据字节数组编码,生成节点和权值,然后构造赫夫曼树,得到对应的赫夫曼编码表和编码后的数组,然后一起压缩到文件中。
*/
public static byte[] huffmanZip(byte[] bytes){
List<Node> nodes = getNodes(bytes);
Node huffmanTree = createHuffmanTree(nodes);
//根据赫夫曼树进行编码
Map<Byte, String> codes = getCodes(huffmanTree);
//根据赫夫曼树编码得到压缩后的赫夫曼字节数组
byte[] zip = zip(bytes, codes);
return zip;
}
//接受字节数组,生成权值
public static List<Node> getNodes(byte[] bytes){
ArrayList<Node> nodes = new ArrayList<Node>();
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集合中,entry是键值对集合
for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
nodes.add(new Node(entry.getKey(),entry.getValue()));
}
return nodes;
}
//根据list创建对应的huffman树
private static Node createHuffmanTree(List<Node> nodes){
while(nodes.size() > 1){
Collections.sort(nodes);
Node node = nodes.get(0);
Node node1 = nodes.get(1);
//构建一个新的二叉树
Node parent = new Node(null,node.weight + node1.weight);
parent.left = node;
parent.right = node1;
nodes.remove(node);
nodes.remove(node1);
nodes.add(parent);
}
return nodes.get(0);
}
//生存huffman树对应的编码
//存储字节和对应的编码
static Map<Byte, String> huffmanCodes = new HashMap<Byte,String>();
static StringBuilder stringBuilder = new StringBuilder();
private static Map<Byte,String> getCodes(Node root){
if(root == null){
return null;
}
getCodes(root.left,"0",stringBuilder);
getCodes(root.right,"1",stringBuilder);
return huffmanCodes;
}
private static void getCodes(Node node,String code,StringBuilder stringBuilder){
StringBuilder stringBuilder1 = new StringBuilder(stringBuilder);
stringBuilder1.append(code);
if(node != null){
if(node.data == null){//非叶子节点
getCodes(node.left,"0",stringBuilder1);
getCodes(node.right,"1",stringBuilder1);
}else{
huffmanCodes.put(node.data,stringBuilder1.toString());//说明找到叶子节点
}
}
}
//根据编码表生成压缩后的数组
private static byte[] zip(byte[] bytes, Map<Byte,String> huffmanCodes){
StringBuilder stringBuilder = new StringBuilder();
for (byte aByte : bytes) {
stringBuilder.append(huffmanCodes.get(aByte));
}
int len;
if(stringBuilder.length() % 8 == 0){
len = stringBuilder.length()/8;
}else{
len = stringBuilder.length()/8 + 1;
}
byte[] bytes1 = 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);
}
bytes1[index] = (byte)Integer.parseInt(strByte,2);
index ++;
}
return bytes1;
}
}
//创建node节点
class Node implements Comparable<Node>{
Byte data;
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 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();
}
}
}