JAVA-数据结构与算法-堆排序和赫夫曼树

arr[i] = arr[k]; //把大的值赋给当前节点

i = k; //将k的值给i,循环结束找到最大值后,将父节点的值给子节点

} else {

break;

}

}

//for循环结束后,已经将i作为父节点的最大值,放在了这个树最顶部

//将temp换到被交换的叶子节点

arr[i] = temp;

}

复制代码

  • 小顶堆

public static void adjustHeap(int[] arr, int i, int length) {

int temp = arr[i];

for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {

if (k + 1 < length && arr[k] > arr[k+1]) {

k++; //k指向右子节点

}

if (arr[k] < temp) {

//如果子节点大于父节点,要交换

arr[i] = arr[k]; //把大的值赋给当前节点

i = k; //将k的值给i,循环结束找到最大值后,将父节点的值给子节点

} else {

break;

}

}

arr[i] = temp;

}

复制代码

赫夫曼树

====

  • 给定n个权值,作为n个叶子节点,若该树的带权路径长度(wpl)达到最小,则为最优二叉树,也叫赫夫曼树,权值越大的节点离根越近

  • 路径,从一个节点,往下可以达到孩子或者孙子节点之间的通路

  • 路径长度,规定根节点层数为-1,到第L层节点的路径长度为L-1

  • 带权路径长度,给节点一个权重值,从根节点到该节点之间的路径长度与该节点的权的乘积

  • 树的带权路径长度,所有叶子节点的带权路径之和,记为weighted path length,权值越大的节点距离根节点越近,最小的就是赫夫曼树/最优二叉树

public static HuffmanNode createHuffmanTree(int[] arr) {

//放入list

ArrayList nodes = new ArrayList<>();

for (int i : arr) {

nodes.add(new HuffmanNode(i));

}

while (nodes.size() > 1) {

//排序

Collections.sort(nodes);

//取出根节点权重值最小的两棵树

HuffmanNode left = nodes.get(0);

HuffmanNode right = nodes.get(1);

//构建新的二叉树

HuffmanNode parent = new HuffmanNode(left.value + right.value);

parent.left = left;

parent.right = right;

//从list中删除

nodes.remove(left);

nodes.remove(right);

//将新构建的加入

nodes.add(parent);

}

//返回赫夫曼树的头

return nodes.get(0);

}

复制代码

  • 节点

//重写排序方法

class HuffmanNode implements Comparable {

int value;

HuffmanNode left;

HuffmanNode right;

public HuffmanNode(int value) {

this.value = value;

}

@Override

public String toString() {

return “HuffmanNode{” +

“value=” + value +

‘}’;

}

@Override

public int compareTo(HuffmanNode o) {

//升序

return this.value - o.value;

}

//前序遍历

public static void preOrderList(HuffmanNode node) {

System.out.println(node);

if (node.left != null) {

preOrderList(node.left);

}

if (node.right != null) {

preOrderList(node.right);

}

}

}

复制代码

赫夫曼编码


  • 算法,数据文件压缩,可变字长编码(VLC)的一种

  • 变长编码,统计每个字符出现的次数,字数越多,对应的二进制位越少,但是会有多义性

  • 赫夫曼编码,无损压缩,将字符出现的次数构建成一颗赫夫曼树,次数作为权值;向左为0,向右为1,按照根节点到叶子节点的路径作为这个字符的编码,避免多义性

  • 借助了赫夫曼树的特点权值越大离根节点越近,那么字符出现次数越多,编码长度越小

  • 如果赫夫曼树中有多个相同的权重值,会有可能导致形成的树的结构不一样,但是wpl是一样的,这样生成的赫夫曼编码不一样,但是压缩后的大小/长度是一样的

压缩

  • 效果,将一个字节数组转换成赫夫曼编码字节数组

/**

  • 返回赫夫曼编码字节数组

  • @param bytes 原始字节数组

  • @return

*/

private static byte[] huffmanZip(byte[] bytes) {

List nodes = getNodes(bytes);

HuffmanCodeNode node = createHuffmanTree(nodes);

Map<Byte, String> huffmanCodes = getHuffmanCodes(node);

return zip(bytes, huffmanCodes);

}

复制代码

  • 先将bytes转换成list,便于生成赫夫曼树

public static List getNodes(byte[] bytes) {

ArrayList nodes = new ArrayList<>();

//遍历bytes统计每个出现的次数

HashMap<Byte, Integer> map = new HashMap<>();

Integer count = 0;

//存入字符-字符个数

for (byte b : bytes) {

count = map.get(b);

if (count == null) {

map.put(b, 1);

} else {

map.put(b, count + 1);

}

}

//把map转换成node对象,node对象包括字符权重(次数)

map.forEach((b, val) -> nodes.add(new HuffmanCodeNode(b,val)));

return nodes;

}

复制代码

  • 生成赫夫曼树,获取根节点

public static HuffmanCodeNode createHuffmanTree(List nodes) {

while (nodes.size() > 1) {

Collections.sort(nodes);

HuffmanCodeNode left = nodes.get(0);

HuffmanCodeNode right = nodes.get(1);

//创建新的二叉树节点,没有字符,只有值

HuffmanCodeNode parent = new HuffmanCodeNode(null, left.weight + right.weight);

parent.left = left;

parent.right = right;

nodes.remove(left);

nodes.remove(right);

nodes.add(parent);

}

return nodes.get(0);

}

}

复制代码

  • 生成赫夫曼编码表

//编码表,放在map<Byte, String>

static Map<Byte, String> huffmanCodes = new HashMap<>();

//负责拼接编码

static StringBuilder stringBuilder = new StringBuilder();

//重载,调用时直接传入根节点

public static Map<Byte, String> getHuffmanCodes(HuffmanCodeNode node) {

if (node == null) {

return null;

}

getHuffmanCodes(node.left, “0”, stringBuilder);

getHuffmanCodes(node.right, “1”, stringBuilder);

return huffmanCodes;

}

/**

  • 将传入的node节点的所有叶子节点的赫夫曼编码得到,并放入的huffmanCodes

  • @param node 传入根节点

  • @param code 路径 左子节点为0 右子节点为1

  • @param stringBuilder 拼接路径

*/

public static void getHuffmanCodes(HuffmanCodeNode node, String code, StringBuilder stringBuilder) {

//生成一个新的StringBuilder,因为每次遇到非叶子节点,都会进入递归,相当于进行了分叉

//所以,每次进入递归,都要再次生成一个新的,否则会重复拼接

StringBuilder stringCode = new StringBuilder(stringBuilder);

stringCode.append(code);

if (node != null) {

//判断当前是什么节点

if (node.data == null) {

//非叶子节点,递归处理

//左

getHuffmanCodes(node.left, “0”,stringCode);

//右

getHuffmanCodes(node.right,“1”,stringCode);

} else {

//找到某个叶子节点

huffmanCodes.put(node.data, stringCode.toString());

}

}

}

复制代码

  • 生成对应赫夫曼编码字节数组,因为字节数组转换成二进制字符串的时候,末尾如果是0开头的,开头将会被舍去,所以要另外用endString记录

//存放结尾的编码

static String endString = “”;

/**

  • 将一个字符串对应的byte数组,通过赫夫曼编码表,返回赫夫曼编码压缩后的byte数组

  • @param bytes 原始字符数组

  • @param huffmanCodes 经过赫夫曼编码处理后的字符编码

  • @return 原始字符编码数组

  • java的数字都是以补码的形式出现的,byte要转为数字,也要把补码转换成原码

  • 正数三码合一

  • 负数补码 = 原码保持符号为不变按位取反 + 1

  • byte[] 一个字节存8位带符号数的二进制 需要-> -1 反码 ->保留符号为,取反转换成原码->十进制

  • 10101000(补码) => 10101000 - 1 => 10100111 取反 => 11011000 => -88

*/

private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {

StringBuilder stringBuilder = new StringBuilder();

//获取字符对应的赫夫曼编码,并拼接

for (byte b : bytes) {

stringBuilder.append(huffmanCodes.get(b));

}

//转换成byte数组

//如果不能被8整除,加上7,一定能被8整除;如果能被8整除,加上7,多出来的部分也不会影响结果

//int len = (stringBuilder.length() + 7) / 8;

int len = stringBuilder.length() % 8 == 0 ? stringBuilder.length() / 8 : stringBuilder.length() / 8 + 1;

if (stringBuilder.length() - (len - 1) * 8 != 0) {

//处理末尾

endString = stringBuilder.substring((len - 1) * 8, stringBuilder.length());

}

//创建存储压缩后的byte数组

byte[] huffmanCodeBytes = new byte[len];

String strByte;

//记录第几个byte

int index = 0;

for (int i = 0; i < stringBuilder.length(); i += 8) {

if (i + 8 > stringBuilder.length() - 1) {

//不够8位

strByte = stringBuilder.substring(i);

} else {

strByte = stringBuilder.substring(i, i + 8);

}

huffmanCodeBytes[index] = (byte) Integer.parseInt(strByte,2);

index ++;

}

return huffmanCodeBytes;

}

复制代码

解压

  • 调用

byte[] source = decode(huffmanCodes, res);

复制代码

  • 完成对压缩数据的解码,本质就是将压缩完成的字节数组和对应的赫夫曼编码表传入,解码成原来的字节数组

/**

  • @param huffmanCodes 赫夫曼编码表

  • @param huffmanBytes 赫夫曼编码得到的字节数组,被解压的数组

  • @return

*/

private static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {

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]));

}

//按照编码表解码

//要将编码表调转

HashMap<String, Byte> map = new HashMap<>();

huffmanCodes.forEach((b, s) -> map.put(s,b));

//创建集合存放byte

ArrayList list = new ArrayList<>();

int count;

for (int i = 0; i < stringBuilder.length(); i += count) {

//扫描对应的二进制字符串

count = 1;

boolean flag = true;

Byte b = null;

while (flag) {

//取出一位

//让count移动,直到取到一个存在的字符

String key = stringBuilder.substring(i, i + count);

b = map.get(key);

if (b == null) {

count ++;

} else {

//匹配到

flag = false;

}

}

list.add(b);

//i移动到count的位置

//i += count;

}

//循环结束后,存放所有的字符

byte[] bytes = new byte[list.size()];

for (int i = 0; i < bytes.length; i++) {

bytes[i] = list.get(i);

}

return bytes;

}

复制代码

  • 将一个byte转换成二进制字符串

/**

  • 将一个byte转换成二进制字符串

  • @param flag 标志是否需要补高位,如果是true需要补高位,如果false不补;如果是最后一个字节不需要补高位

  • @param b 对应的是一个字节,二进制的字符串,是按补码的形式

  • @return

*/

private static String byteToBitString(boolean flag, byte b) {

//使用变量保存b

//将b转换成int

int temp = b;

if (flag) {

//2^8

temp |= 256; //按位或

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
n flag, byte b) {

//使用变量保存b

//将b转换成int

int temp = b;

if (flag) {

//2^8

temp |= 256; //按位或

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-tY2LjUZo-1715709168261)]

[外链图片转存中…(img-qc8X0BzK-1715709168261)]

[外链图片转存中…(img-AO9I3W2P-1715709168262)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值