目录

一、创建赫夫曼树

代码实现:最后返回值式创建好的赫夫曼树的顶点

对int[] arr = {13, 7, 8, 3, 29, 6, 1};进行赫夫曼树,我们创建好的node数组依次是这样变化

创建节点:节点有前序遍历方法,之后遍历树时只需利用树的根节点来调用遍历方法

遍历方法:

二、赫夫曼编码的应用(压缩->还原)

代码实现

把字符串转为字节数组

把字节数组转为带有字节和此字节出现次数的list集合并返回

创建赫夫曼树并返回根节点(遍历用)

生产赫夫曼编码(使用Map来存储对应关系)

将原来的字符串按照赫夫曼编码组合起来(用StringBuilder)并以八位一分割转为10进制数字构成的数组

还原:根据赫夫曼编码和数字数组还原为二进制编码,再将Map翻转,依据Map把二进制编码转为Ascii码,再根据Ascii码还原为英文的字符串

在将字节转为二进制字符串时有一点要注意,传输过来是补码,需要分情况还原为源码

三、赫夫曼编码的使用例子

压缩

解压


一、创建赫夫曼树

构成赫夫曼树的步骤:

1) 从小到大进行排序, 将每一个数据,每个数据都是一个节点 , 每个节点可以看成是一颗最简单的二叉树

2) 取出根节点权值最小的两颗二叉树

3) 组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和

4) 再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复  1-2-3-4 的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树

代码实现:最后返回值式创建好的赫夫曼树的顶点

public static Node createHuffmanTree(int[] arr) {//数组不支持排序,我们把数组元素装到Node中,然后把这些Node装到list中ArrayList<Node> list = new ArrayList<>();for (int i = 0; i < arr.length ; i++) {list.add(new Node(arr[i]));//节点的创建只有一个左指针和右指针以及自己的值}//循环操作的过程while (list.size() > 1) {//排序:从小到大Collections.sort(list);//取出根节点权值最小的两个二叉树,此时一个节点就是一个二叉树//①取出第一个值Node leftNode = list.get(0);//②取出第二个值Node rightNode = list.get(1);//③构建成树Node parent = new Node(leftNode.value + rightNode.value);parent.left = leftNode;parent.right = rightNode;//从list中删除leftNode,rightNodelist.remove(leftNode);list.remove(rightNode);//把parent在加入到list中list.add(parent);System.out.println(list);}
//得到了赫夫曼树的根节点return list.get(0);}
}

对int[] arr = {13, 7, 8, 3, 29, 6, 1};进行赫夫曼树,我们创建好的node数组依次是这样变化

[Node{value=6}, Node{value=7}, Node{value=8}, Node{value=13}, Node{value=29}, Node{value=4}]
[Node{value=7}, Node{value=8}, Node{value=13}, Node{value=29}, Node{value=10}]
[Node{value=10}, Node{value=13}, Node{value=29}, Node{value=15}]
[Node{value=15}, Node{value=29}, Node{value=23}]
[Node{value=29}, Node{value=38}]
[Node{value=67}]

创建节点:节点有前序遍历方法,之后遍历树时只需利用树的根节点来调用遍历方法

//创建节点类
//为了让节点实现排序,所以让它实现comparable接口
class Node implements Comparable<Node> {int value;Node left;Node right;//前序遍历的方法public void perOrder() {System.out.println(this);if (this.left != null) {this.left.perOrder();}if (this.right != null) {this.right.perOrder();}}public Node(int value) {this.value = value;}@Overridepublic String toString() {return "Node{" +"value=" + value +'}';}@Overridepublic int compareTo(Node n) {return this.value - n.value;}
}

遍历方法:

 public static void perOrder(Node root) {if (root != null) {root.perOrder();} else {System.out.println("空的树,没法遍历");}}

遍历结果:

Node{value=67}
Node{value=29}
Node{value=38}
Node{value=15}
Node{value=7}
Node{value=8}
Node{value=23}
Node{value=10}
Node{value=4}
Node{value=1}
Node{value=3}
Node{value=6}
Node{value=13}

二、赫夫曼编码的应用(压缩->还原)

1) 传输一个字符串:i like like like java do you like a java  (包括空格在内有40个字符)

2)在通信领域中信息的处理方式1-定长编码

对应的Ascii码为: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

3)此时转为二进制为: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位)

4)  在通信领域中信息的处理方式2-变长编码

d:1 y:1 u:1 j:2  v:2  o:2  l:4  k:4  e:4 i:5  a:5   :9  // 各个字符对应的个数

按照上面字符出现的次数构建一颗赫夫曼树, 次数作为权值

5)在赫夫曼树的基础上左边用0表示,右边用1表示

6) 此时每一个字母有了自己对应的二进制编码,而且编码有一个特点:从第一位开始不会有重复的,每一个都是独立的,不会出现长的编码包含短的编码的情况

o: 1000   u: 10010  d: 100110  y: 100111  i: 101

a : 110     k: 1110    e: 1111       j: 0000       v: 0001

l: 001          : 01

7)  按照上面的赫夫曼编码,我们的"i like like like java do you like a java"   字符串对应的编码为 (注意这里我们使用的无损压缩)

1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110  通过赫夫曼编码处理  长度为  133

8)此时赫夫曼编码的作用体现出来了,因为计算机底层传输都是用的二进制编码,我们把原先的355位压缩到了133位

9)此时传输并不奏效,反而加大了工作量,原先的40个字符,现在有133个,但是二进制数可以八位为一体成为字节,所以变为[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]为17位

10)把这17为数字和赫夫曼编码集一同发送,按照赫夫曼编码集还原,从而实现数据的压缩--->还原

代码实现

把字符串转为字节数组

String str = "i like like like java do you like a java";byte[] bytes = str.getBytes();

此时字符串变为:

[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]

把字节数组转为带有字节和此字节出现次数的list集合并返回

public static List<Node> toList(byte[] bytes) {ArrayList<Node> list = new ArrayList<>();
//        遍历bytes,统计每一个byte出现的次数,用map的key和value表示HashMap<Byte, Integer> map = new HashMap<>();for (Byte b : bytes) {Integer count = map.get(b);if (count == null) {map.put(b, 1);} else {map.put(b, count + 1);}}//        把每一个键值对转成Node对象,并加入到nodes集合中for (Map.Entry<Byte, Integer> e : map.entrySet()) {list.add(new Node(e.getKey(), e.getValue()));}return list;}

此时list为:

[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}]

创建赫夫曼树并返回根节点(遍历用)

 public static Node creadHuffmanTree(List<Node> list) {while (list.size() > 1) {Collections.sort(list);Node leftNode = list.get(0);Node rightNode = list.get(1);Node parent = new Node(null, leftNode.weight + rightNode.weight);parent.left = leftNode;parent.right = rightNode;list.remove(leftNode);list.remove(rightNode);list.add(parent);}return list.get(0);}

根节点为:Node{data=null, weight=40}

生产赫夫曼编码(使用Map来存储对应关系)

//重载以下方法public static Map<Byte, String> creatHuffmanCodes(Node root) {if (root != null) {creatHuffmaCodes(root.left, "0", stringBuilder);creatHuffmaCodes(root.right, "1", stringBuilder);return huffmanCodes;} else {return null;}}//    生成赫夫曼树对应的赫夫曼编码
//    思路:先考虑生成的码存放在哪里:我们用Map<Byte,String>来存放static Map<Byte, String> huffmanCodes = new HashMap<Byte, String>();//          在生成赫夫曼编码的过程中,需要不断的对路径进行拼接:我们定义StringBuilder来存放叶子节点的路径static StringBuilder stringBuilder = new StringBuilder();public static void creatHuffmaCodes(Node node, String code, StringBuilder stringBuilder) {StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);stringBuilder2.append(code);if (node != null) {//有节点才处理if (node.data == null) {//非叶子节点//向左递归creatHuffmaCodes(node.left, "0", stringBuilder2);//向右递归creatHuffmaCodes(node.right, "1", stringBuilder2);} else {//叶子节点huffmanCodes.put(node.data, stringBuilder2.toString());}}}

此时赫夫曼编码为:

{32=01, 97=100, 100=11000, 117=11001, 101=1110, 118=11011, 105=101, 121=11010, 106=0010, 107=1111, 108=000, 111=0011}

将原来的字符串按照赫夫曼编码组合起来(用StringBuilder)并以八位一分割转为10进制数字构成的数组

public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {StringBuilder stringBuilder = new StringBuilder();for (byte b : bytes) {stringBuilder.append(huffmanCodes.get(b));}System.out.println("stringBuilder:" + stringBuilder);int len;//处理后byte[]的长度if (stringBuilder.length() % 8 == 0) {len = stringBuilder.length() / 8;} else {len = stringBuilder.length() / 8 + 1;}byte[] by = new byte[len];int index = 0;for (int i = 0; i < stringBuilder.length(); i += 8) {String substring;if (i + 8 > stringBuilder.length()) {substring = stringBuilder.substring(i);} else {substring = stringBuilder.substring(i, i + 8);}//子字符串转成byte,放到by中by[index] = (byte) Integer.parseInt(substring, 2);index++;}return by;}

此时StringBuilder(二进制编码)为:

1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100

转为数字数组为:

[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]

还原:根据赫夫曼编码和数字数组还原为二进制编码,再将Map翻转,依据Map把二进制编码转为Ascii码,再根据Ascii码还原为英文的字符串

public static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {//先把数字变成二进制数组StringBuilder stringBuilder = new StringBuilder();for (int i = 0; i < huffmanBytes.length; i++) {//判断是不是最后一个字节boolean flag = (i == huffmanBytes.length - 1);stringBuilder.append(bytesToBiteString(!flag, huffmanBytes[i]));}System.out.println("还原回来的:" + stringBuilder.toString());//把二进制字符串按照赫夫曼编码表进行解码//把赫夫曼编码表进行调换,反向查询HashMap<String, Byte> map = new HashMap<String, Byte>();for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {map.put(entry.getValue(), entry.getKey());}System.out.println("还原回来的:" + map);//创建一个集合,存放byteArrayList<Byte> list = new ArrayList<>();//遍历StringBuilder,与map的key匹配for (int i = 0; i < stringBuilder.length(); ) {int count = 1;//count是用来遍历的,i直接跳到count的位置即可boolean flag = true;Byte b = null;while (flag) {String substring = stringBuilder.substring(i, i + count);b = map.get(substring);if (b == null) {//说明没有匹配到count++;} else {flag = false;}}list.add(b);i += count;}System.out.println("临时的ArraryList:" + list);//for循环结束以后,list中存放了:所有的字符:"i like like like java do you like a java";//把list中的数据放到byte[]中并返回byte[] bytes = new byte[list.size()];for (int i = 0; i < bytes.length; i++) {bytes[i] = list.get(i);}return bytes;}

在将字节转为二进制字符串时有一点要注意,传输过来是补码,需要分情况还原为源码

 /*** 把一个字节转为二进制字符串** @param flag 表示是否需要补高位,如果是true则需要补高位,反之不需要* @param b    传入的一个byte* @return b对应的二进制的字符串(补码形式)*/public static String bytesToBiteString(boolean flag, byte b) {int temp = b;//将b转为int类型,因为Integer有toBinaryString()//如果是正数,要补高位if (flag) {temp |= 256;//按位与 1 0000 0000 | 0000 0001 =>1 0000 0001}String s = Integer.toBinaryString(temp);//返回的是temp对应的二进制的补码if (flag) {return s.substring(s.length() - 8);} else {return s;}}

三、赫夫曼编码的使用例子

我们上述的代码实现了压缩与还原,将其封装后便成了赫夫曼编码压缩文件和还原文件的两个方法

又因为赫夫曼编码是字节码,所以既可以实现文件的压缩,又可以实现图片、视频的压缩。有兴趣可以试试

文件的压缩传输解压需要用到io流

压缩

/*** @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[]byte[] b = new byte[is.available()];//读取文件is.read(b);//直接对源文件压缩byte[] huffmanBytes = getHuffmanCodesBytes(b);//创建文件的输出流, 存放压缩文件os = new FileOutputStream(dstFile);//创建一个和文件输出流关联的ObjectOutputStreamoos = new ObjectOutputStream(os);//把 赫夫曼编码后的字节数组写入压缩文件oos.writeObject(huffmanBytes); //我们是把//这里我们以对象流的方式写入 赫夫曼编码,是为了以后我们恢复源文件时使用//注意一定要把赫夫曼编码 写入压缩文件oos.writeObject(huffmanCodes);} catch (Exception e) {// TODO: handle exceptionSystem.out.println(e.getMessage());} finally {try {is.close();oos.close();os.close();} catch (Exception e) {// TODO: handle exceptionSystem.out.println(e.getMessage());}}}

解压

   /*** @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);//创建一个和  is关联的对象输入流ois = new ObjectInputStream(is);//读取byte数组  huffmanBytesbyte[] 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) {// TODO: handle exceptionSystem.out.println(e.getMessage());} finally {try {os.close();ois.close();is.close();} catch (Exception e2) {// TODO: handle exceptionSystem.out.println(e2.getMessage());}}}

赫夫曼树的创建,赫夫曼编码的原理及使用相关推荐

  1. 赫夫曼树的创建(思路分析)

    赫夫曼树的创建(思路分析) 构成赫夫曼树的步骤: 从小到大进行排序,将有一个数据(每一个数据其实就是一个节点)看做是一颗最简单的二叉树 取出根节点权值最小的两颗二叉树(其实就是取出权值最小的两个结点) ...

  2. 2020-10-1 //严蔚敏《数据结构》 //赫夫曼树及其应用:创建顺序赫夫曼树创建及得到赫夫曼编码

    //严蔚敏<数据结构> //赫夫曼树及其应用:创建顺序赫夫曼树创建及得到赫夫曼编码 //(从叶子结点到根逆向求每个字符的赫夫曼编码)以及(无栈非递归遍历赫夫曼树,求赫夫曼编码) //自学中 ...

  3. 赫夫曼树介绍、赫夫曼树的性质、赫夫曼编码、赫夫曼树与赫夫曼编码的应用

    文章目录 赫夫曼树 1. 赫夫曼树介绍: 2. 赫夫曼树的创建过程: 3. 赫夫曼树的性质: 4. 赫夫曼编码: 5. 赫夫曼树与赫夫曼编码的c语言代码实现: 赫夫曼树 1. 赫夫曼树介绍: ​ 赫夫 ...

  4. 一文了解赫夫曼树的构建与赫夫曼编码

    文章目录 一.赫夫曼树 基本介绍 赫夫曼树几个重要概念和举例说明 赫夫曼树创建步骤图解 代码构建赫夫曼树 二.赫夫曼编码 1基本介绍 通信领域中的信息的处理方式1-定长编码 通信领域中的信息的处理方式 ...

  5. 赫夫曼树与赫夫曼编码

    1.赫夫曼树也叫最优二叉树,n个权值构造一颗有n个叶子结点的二叉树,且使叶子结点带权路径长度之和最小,则得到一颗赫夫曼树. 2.赫夫曼树的构造 ⑴给定n个权值,构成一个森林的集合F,F中初始为n颗只有 ...

  6. 三十、赫夫曼树的设计与代码实现

    一.基本介绍 给定 n 个权值作为 n 个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为 最优二叉树,也称为哈夫曼树(Huffman Tree), 还有的书翻译为霍 ...

  7. 数据结结构学习 ---赫夫曼树

    ------ 赫夫曼树和赫夫曼编码的存储表示------ typedef struct {unsigned int weight;unsigned int parent,lchild,rchild; ...

  8. 数据结构--赫夫曼树及其应用

    讲解请参考 赫夫曼 ------ 赫夫曼树和赫夫曼编码的存储表示------ typedef struct {unsigned int weight;unsigned int parent,lchil ...

  9. 哈/赫夫曼树(最优二叉树)

    一.哈/赫夫曼树的基本定义 (1)路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径. (2)路径长度:路径上的分支数目. (3)树的路径长度:从树根到每一个结点的路径长度之和.(完全 ...

最新文章

  1. 机器学习——模型测试与评估方法与指标
  2. Oracle SQL高级编程——分析函数(窗口函数)全面讲解
  3. MySQL中 Order By 和 Limit 的排序问题
  4. bzoj 2375: 疯狂的涂色
  5. 数学图形(2.23)Cylindric sine wave柱面正弦曲线
  6. TCP之深入浅出send和recv
  7. 【阿里云域名】我都有服务器了,为什么还要购买域名?
  8. s4-2 ALOHA 协议
  9. 武汉疫情之后,中国即将发生的10大变化!(强烈推荐)
  10. Java Socket编程 文件传输(客户端从服务器下载一个文件)
  11. JSK-61 二进制加法【大数】(废除!!!)
  12. bzoj 3262: 陌上花开(cdq分治)
  13. MATLAB 2020b版本发布,下载试用版并上手使用记录。
  14. 敏感词过滤/字符编码
  15. 小白学习meshlab(1)——基本的edit工具学习
  16. 博弈论读书笔记(二):纳什均衡与野猪博弈
  17. 我的世界服务器无限开号,我的世界开挂指令大全表一览!39条命令无限可能性
  18. 【渝粤题库】陕西师范大学200611 英语修辞 作业
  19. win10 vs2022 .net6 opencvsharp 4.5.5自己编译wecharts 微信二维码扫描模块。
  20. java 模拟贷款实现等额本息还款

热门文章

  1. 考研时间查询时间,已经出炉啦
  2. Git 提交模板 Commit Template
  3. java 类型判断方法
  4. 联署计划-三赢的网络营销
  5. (C语言)输入百分制成绩(0-100间整数),输出相应的五级制成绩(A-E)。五级制与百分制的对应关系为:A-[90,100]、B-[80,89]、C-[70,79]、D-[60,69]、E-[0,5
  6. 使用ggplot2进行数据可视化—坐标系(七)
  7. springboot 好玩的自定义设置——启动时的banner
  8. 服务器可不可以装win10系统,云服务器可以安装win10吗
  9. FTP文件传输服务器(详解)
  10. JavaXYQ 1.4 M1 - 完整的RPG游戏