赫夫曼编码(前缀编码)
以读取txt文件或者自己编写字符串来模拟编码
需要注意进行赫夫曼树产生赫夫曼编码的递归过程
import lombok.Data;
import java.io.*;
import java.util.*;
public class HuffmanTreeCode {
static Map<Character, String> huffmanCodeTable = new HashMap<>();
public static void main(String[] args) {
/**
* 首先我们拿到了一个需要经过传输的字符串,但是这个字符串可以经过压缩之后再传输
* 比如str = "hello word!"
* 要是正常传输的话ASCII码的对应关系 h:104 e:101 l:108 o:111 ' ':32 w:119 r:114 d:100 !:33
* 计算机传输信息是二进制传输,所以需要将字母对应的ascii码转成二进制后再传输
* 而如果是用通信领域的编码来传输,则 h:1 e:1 l:2 o:2 ' ':1 w:1 r:1 d:1 !:1
* 再进行编码的话如 h:0 e:1 ' ':10 w:11 r:100 d:101 !:110 l:111 o:1000
* 011111111000...
* 上面这种编码机制不是前缀编码,因为不知道是111还是1,会有歧义出现
* 那么就引出了赫夫曼编码,将出现字符的次数按由小到大排序,再构建一棵赫夫曼树
* 然后根据向左为0,向右为1进行赫夫曼编码,这样编码的结果就是前缀编码
* ----------->>>>>>>>>>>
* 拿到了赫夫曼编码表以后需要对原始字符串进行编码
* 每8位取出转成10进制然后传输,最后需要还原出原始的01字符串
* 计算出该01串的最后一个8位的长度,并且将其长度加入需要传输的list中
*/
// String transmitInfo = "hello word!";
String transmitInfo = getTxtFileContent("E:\\java-project\\record.txt");
List<Byte> list = zipToList(transmitInfo);
String dstPath = "E:\\java-project\\record.zip";
zipToFile(list, dstPath);
}
public static String getTxtFileContent(String srcPath) {
StringBuffer srcInfo = new StringBuffer();
BufferedInputStream br = null;
int readLen = 0;
byte[] bytes = new byte[1024];
try {
br = new BufferedInputStream(new FileInputStream(srcPath));
while((readLen = br.read(bytes)) != -1) {
srcInfo.append(new String(bytes, 0 ,readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return srcInfo.toString();
}
public static void zipToFile(List<Byte> list, String dstPath) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(dstPath));
oos.writeObject(list);
oos.writeObject(huffmanCodeTable);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static List<Byte> zipToList(String str) {
Trans root = getHuffmanRoot(str);
createCodeTable(root, "", "");
String zeroOneStr = getZeroOneString(str);
return transformToDecimal(zeroOneStr);
}
//对这一串01码每8位取一次转成10进制
public static List<Byte> transformToDecimal(String str) {
int len = str.length() / 8;
int remain = str.length() % 8;
List<Byte> list = new ArrayList<>();
for (int i = 0; i < len * 8; i+=8) {
list.add((byte)Integer.parseInt(str.substring(i, i + 8), 2));
}
if(remain > 0) list.add((byte)Integer.parseInt(str.substring(str.length() - remain), 2));
list.add((byte)remain);
return list;
}
//对原始字符串进行编码,返回一个需要传输的只含有01的字符串
public static String getZeroOneString(String str) {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < str.length(); i++) {
stringBuffer.append(huffmanCodeTable.get(str.charAt(i)));
}
return stringBuffer.toString();
}
//创建成了赫夫曼编码表
public static void createCodeTable(Trans node, String i, String code) {
String str = code + i;
if(node.getValue() == -1) {
createCodeTable(node.getLeft(), "0", str);
createCodeTable(node.getRight(), "1", str);
}else {
huffmanCodeTable.put((char)node.getValue(), str);
}
}
//这是拿到了已创建的赫夫曼树的根节点
public static Trans getHuffmanRoot(String str) {
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
map.put(str.charAt(i), map.getOrDefault(str.charAt(i), 0) + 1);
}
Trans[] trans = new Trans[map.size()];
int index = 0;
for(Map.Entry<Character, Integer> entry: map.entrySet()) {
trans[index ++] = new Trans(entry.getKey(), entry.getValue());
}
List<Trans> list = new ArrayList<>();
for(Trans tran: trans) {
list.add(tran);
}
while(list.size() > 1) { // 构建赫夫曼树的过程
Collections.sort(list);
Trans left = list.remove(0);
Trans right = list.remove(0);
Trans parent = new Trans(-1, left.getWeight() + right.getWeight());
parent.setLeft(left);
parent.setRight(right);
list.add(parent);
}
return list.get(0);
}
}
@Data
class Trans implements Comparable<Trans>{
private int value;
private int weight;
private Trans left;
private Trans right;
public Trans(int value, int weight) {
this.value = value;
this.weight = weight;
}
public int compareTo(Trans o) {
return this.weight - o.weight;
}
@Override
public String toString() {
return "Trans{" +
"value=" + value +
", weight=" + weight +
'}';
}
}
赫夫曼解码过程
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.*;
public class HuffmanTreeDecode {
static Map<Character, String> huffmanCodeTable = new HashMap<>();
public static void main(String[] args) {
String srcPath = "E:\\java-project\\123.zip";
List<Byte> list = unzipFile(srcPath);
String zeroOneStr = decode(list);
String res = getInitialInfo(zeroOneStr);
System.out.println(res);
}
public static String getInitialInfo(String str) {
Map<String, Character> reverseHuffmanCodeTable = new HashMap<>();
for(Map.Entry<Character, String> entry: huffmanCodeTable.entrySet()) {
reverseHuffmanCodeTable.put(entry.getValue(), entry.getKey());
}
//根据传进来的str以及反哈希表还原出原字符串
StringBuffer res = new StringBuffer();
int index = 0;
for (int i = 1; i <= str.length(); i++) {
if(reverseHuffmanCodeTable.containsKey(str.substring(index, i))) {
res.append(reverseHuffmanCodeTable.get(str.substring(index, i)));
index = i;
}
}
return res.toString();
}
public static String decode(List<Byte> list) {
//对最后一个元素要做单独的处理
int remain = list.remove(list.size() - 1);
byte lastEle = list.remove(list.size() - 1);
StringBuffer stringBuffer = new StringBuffer();
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
byte ele = (byte)iterator.next();
if(ele >= 0) stringBuffer.append(Integer.toBinaryString(ele | 256).substring(1));
else stringBuffer.append(Integer.toBinaryString(ele).substring(24));
}
//要看lastEle是正数还是负数
stringBuffer.append((lastEle >= 0)?
Integer.toBinaryString(lastEle | 256).substring(8 - remain + 1):
Integer.toBinaryString(lastEle).substring(24));
return stringBuffer.toString();
}
public static List<Byte> unzipFile(String srcPath) {
ObjectInputStream ois = null;
List<Byte> list = null;
try {
ois = new ObjectInputStream(new FileInputStream(srcPath));
list = (List<Byte>) ois.readObject();
huffmanCodeTable = (Map<Character, String>) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return list;
}
}
一会儿的任务AVL树~~
Avl平衡二叉树实现
注意的地方
首先要会写获取树的最大高度的递归
然后在判断左右子树的高度时,需要在添加一个节点以后对它的父节点都进行左旋或者右旋判断的操作
我自己一开始将左旋右旋的操作写在了Avl树中,然后解决不了部分的特殊的问题,如{10, 7, 11, 6, 8, 9}
因此要注意将左旋右旋的代码写在节点类中
主要还是Avl树的思想比较重要
import lombok.Data;
public class AvlTreeDemo {
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5,6,7,8,9,10,12,45,78,-9,-8,-7,-6};
// int[] arr = new int[]{10, 7, 11, 6, 8, 9};
// int[] arr = new int[]{4, 3, 6, 5, 7, 8};
AvlTree avlTree = getAvlTreeObj(getNodes(arr));
avlTree.infixList();
System.out.println(avlTree.getHeight());
System.out.println(avlTree.getLeftHeight());
System.out.println(avlTree.getRightHeight());
}
public static AvlTree getAvlTreeObj(SNode[] sNodes) {
AvlTree avlTree = new AvlTree();
for(SNode sNode: sNodes) {
avlTree.addNode(sNode);
}
return avlTree;
}
public static SNode[] getNodes(int[] arr) {
SNode[] sNodes = new SNode[arr.length];
for (int i = 0; i < arr.length; i++) {
sNodes[i] = new SNode(arr[i]);
}
return sNodes;
}
}
class AvlTree {
private SNode root;
public void leftRotate() {
SNode newNode = new SNode(this.root.getVal());
newNode.setLeft(this.root.getLeft());
newNode.setRight(this.root.getRight().getLeft());
this.root.setVal(this.root.getRight().getVal());
this.root.setLeft(newNode);
this.root.setRight(this.root.getRight().getRight());
}
public void rightRotate() {
SNode newNode = new SNode(this.root.getVal());
newNode.setRight(this.root.getRight());
newNode.setLeft(this.root.getLeft().getRight());
this.root.setVal(this.root.getLeft().getVal());
this.root.setRight(newNode);
this.root.setLeft(this.root.getLeft().getLeft());
}
public void addNode(SNode sNode) {
if(this.root == null) {
this.root = sNode;
return;
}
this.root.addNode(sNode);
}
public void infixList() {
if(this.root == null) return;
this.root.infixList();
}
public int getHeight() {
if(this.root == null) return 0;
return this.root.getHeight();
}
public int getLeftHeight() {
if(this.root == null) return -1;
return this.root.getLeftHeight();
}
public int getRightHeight() {
if(this.root == null) return -1;
return this.root.getRightHeight();
}
}
@Data
class SNode {
private int val;
private SNode left;
private SNode right;
public SNode(int val) {
this.val = val;
}
public void infixList() {
if(this.left != null) this.left.infixList();
System.out.println(this);
if(this.right != null) this.right.infixList();
}
@Override
public String toString() {
return "SNode{" +
"val=" + val +
'}';
}
public void leftRotate() {
SNode newNode = new SNode(this.val);
newNode.left = this.left;
newNode.right = this.right.left;
this.val = this.right.val;
this.left = newNode;
this.right = this.right.right;
}
public void rightRotate() {
SNode newNode = new SNode(this.val);
newNode.right = this.right;
newNode.left = this.left.right;
this.val = this.left.val;
this.right = newNode;
this.left = this.left.left;
}
public void addNode(SNode sNode) {
if(sNode.val < this.val) {
if(this.left != null) this.left.addNode(sNode);
else this.left = sNode;
}
else {
if(this.right != null) this.right.addNode(sNode);
else this.right = sNode;
}
//在每加入一个节点以后都要进行判断根节点的左右子树是否差的绝对值大于1
if(this.getRightHeight() - this.getLeftHeight() > 1) {
//进行左旋
if(this.right.getLeftHeight() > this.right.getRightHeight()) this.right.rightRotate();
this.leftRotate();
}
if(this.getLeftHeight() - this.getRightHeight() > 1) {
if(this.left.getRightHeight() > this.left.getLeftHeight()) this.left.leftRotate();
this.rightRotate();
}
}
//返回以当前节点为根节点的树的高度
public int getHeight() {
return Math.max((this.left != null)?this.left.getHeight():0,(this.right != null)?this.right.getHeight():0) + 1;
}
public int getLeftHeight() {
return (this.left != null)?this.left.getHeight():0;
}
public int getRightHeight() {
return (this.right != null)?this.right.getHeight():0;
}
}
后序学习了dfs和bfs,目前理解的还不是很透彻,先复习到这,后面几天先巩固贪心,动态规划之类的算法,有自己理解了的题会继续上传到博客上监督自己~~