我们经常会用到文件压缩,压缩之后文件会变小,便于传输,使用的时候又将其解压出来。为什么压缩之后会变小,而且压缩和解压也不会出错。赫夫曼编码和赫夫曼树了解一下。

赫夫曼树:

它是一种的叶子结点带有权重的特殊二叉树,也叫最优二叉树。既然出现最优两个字肯定就不是随便一个叶子结点带有权重的二叉树都叫做赫夫曼树了。

赫夫曼树中有一个很重要的概念就是带权路径,带权路径最小的才是赫夫曼树。

树的路径长度是从根结点到每一个结点的长度之和,带权路径就是每一个结点的长度都乘以自己权重,记做WPL。

假设有abcd数据,权重分别是7 5 2 4。下面构建出来的三棵带权二叉树。

A树:WPL=7*2+5*2+2*2+4*2=36

B树:WPL=7*3+5*3+2*1+4*2=46

C树:WPL=7*1+5*2+2*3+4*3=35

显然C树的带权是最小的。而且无构建出比它更小的了。所以C树就是赫夫曼树

我们从C树发现了一个问题,就是要使得树的带权路径最小,那么权重越大的就应该离根结点越近。所以如果要构建一棵赫夫曼树,首先一定要将数据按权重排序。这是不是就是之前提到的贪心算法,一定有排序,从局部最优到整体最优。

赫夫曼编码:

我们都知道以前的地下党发送电报。都是加密了发送,然后使用密码本来解密。

我们还是发送上面的abcd

显然计算机的世界都是0和1,假设我们用三位来表示上面的字符。也就相当于制作一个密码本

a:000

b:001

c:010

d:011

那么我要传输的就变成了000001010011,然后收到之后按照三位一分来解密就可以了。但是如果数据很多之后。我们可能就不能不用3位来表示了,可能是8位,10位之类了的,那么这个二进制串的长度也相当可怕了。

再看赫夫曼树,如果我们将上面的C图的每一个左分支表示0,右分支表示1

那么现在表示abcd就可以用每个结点长度路径上的值来表示了

a:0

b:10

c:110

d:111

abcd就可以表示为010110111,就从刚才的000001010011的12位缩减到了9位,如果数据量大,这个减少的位数是很可观的。

但是又有一个问题了,这样出来的编码长度不等,其实很容易混淆,所以要设计这种长短不等的编码,必须任意字符的编码都不是另一个字符编码的前缀,这种编码称做前缀编码。显然通过二叉树这样构造出来的编码,每个叶子结点都不同的编码。而这棵赫夫曼树就是我们的密码本。也就是说编码于解码都需要用同样结构的赫夫曼树。

解码:

每次从根开始寻找,找到叶子结点为止,然后又从根开始寻找,比如010110111,

0走左边,左边第一个就是叶子结点,所以找到a,

回到根继续寻找,编码串还剩下10110111,

1走右边,0走左边找到b,110 ->c, 111->d

一般来说设要编码的字符集{c1,c2,c3...},设置各个字符出现的频率{w1,w2,w3...},以各字符作为叶子结点,以相应的频率作为权重来构造赫夫曼树。

赫夫曼树的构建

以我们上面的a:7 b:5 c:4 d:2为例。

1.上面从树的特点来看,首先我们需要按照权重从小到大排序,注意赫夫曼树的构建是逆向构建的,就是说是从叶子结点往根结点构建。排序:d:2  c:4  b:5  a:7

2.取前面两个权值最小结点作为新结点n1的两个子结点,注意二叉树的左小右大规则。新结点的权重为两孩子权重之和,将操作过的结点从数据中移除,新结点放进去继续操作:

n1的权重是 cd权重之和为6,新的排序:b:5  n1:6  a:7

3.取出b和n1构成新作为新结点n2的两个子结点剩余。 新的排序:a:7  n2:11

直到操作到最后两个结点结束。

如果遇到操作的两个结点在已有的数上面还没有,那就另开一个子树,等到操作这个新子树的根结点的时候,再把这棵子树直接移植过去,比如这个数据来构建a:3 b:24 c:6 d:20 e:34 f:4 g:12

排序:a:3  f:4  c:6  g:12  d:20  b:24  e:34

d:20 和b:24 构造出来的子树就是后面移植上去的

代码实现:

现在就按照上面的逻辑,代码实现赫夫曼树的构建和编码解码,对比上面的第二个数据验证结果

  1. package com.nijunyang.algorithm.tree;
  2.  
  3. import java.util.*;
  4.  
  5. /**
  6. * Description: 哈夫曼树
  7. * Created by nijunyang on 2020/4/28 21:43
  8. */
  9. public class HuffmanTree {
  10.  
  11. private static final byte ZERO = 0;
  12. private static final byte ONE = 1;
  13. HuffmanNode root;
  14. Map<Character, Integer> weightMap; //字符对应的权重
  15. List<HuffmanNode> leavesList; // 叶子
  16. Map<Character, String> leavesCodeMap; // 叶子结点的编码
  17.  
  18. public HuffmanTree(Map<Character, Integer> weightMap) {
  19. this.weightMap = weightMap;
  20. this.leavesList = new ArrayList<>(weightMap.size());
  21. this.leavesCodeMap = new HashMap<>(weightMap.size());
  22. creatTree();
  23. }
  24.  
  25. public static void main(String[] args) {
  26. Map<Character, Integer> weightMap = new HashMap<>();
  27. //a:3 f:4 c:6 g:12 d:20 b:24 e:34
  28. weightMap.put('a', 3);
  29. weightMap.put('b', 24);
  30. weightMap.put('c', 6);
  31. weightMap.put('d', 20);
  32. weightMap.put('e', 34);
  33. weightMap.put('f', 4);
  34. weightMap.put('g', 12);
  35. HuffmanTree huffmanTree = new HuffmanTree(weightMap);
  36. //abcd: 1011001101000
  37. String code = huffmanTree.encode("abcd");
  38. System.out.println(code);
  39. System.out.println("1011001101000".equals(code));
  40. String msg = huffmanTree.decode(code);
  41. System.out.println(msg);
  42.  
  43. }
  44.  
  45. /**
  46. * 构造树结构
  47. */
  48. private void creatTree() {
  49. PriorityQueue<HuffmanNode> priorityQueue = new PriorityQueue<>();
  50. weightMap.forEach((k,v) -> {
  51. HuffmanNode huffmanNode = new HuffmanNode(k, v);
  52. priorityQueue.add(huffmanNode);
  53. leavesList.add(huffmanNode);
  54. });
  55. int len = priorityQueue.size();//先把长度取出来,因为等下取数据队列长度会变化
  56.  
  57. //HuffmanNode实现了Comparable接口,优先队列会帮我们排序,我们只需要每次弹出两个元素就可以了
  58. for (int i = 0; i < len - 1; i++) {
  59. HuffmanNode huffmanNode1 = priorityQueue.poll();
  60. HuffmanNode huffmanNode2 = priorityQueue.poll();
  61. int weight12 = huffmanNode1.weight + huffmanNode2.weight;
  62.  
  63. HuffmanNode parent12 = new HuffmanNode(null, weight12); //父结点不需要数据直接传个null
  64. parent12.left = huffmanNode1; //建立父子关系,因为排好序的,所以1肯定是在左边,2肯定是右边
  65. parent12.right = huffmanNode2;
  66. huffmanNode1.parent = parent12;
  67. huffmanNode2.parent = parent12;
  68. priorityQueue.add(parent12); //父结点入队
  69. }
  70. root = priorityQueue.poll(); //队列里面的最后一个即是我们的根结点
  71.  
  72. /**
  73. * 遍历叶子结点获取叶子结点数据对应编码存放起来,编码时候直接拿出来用
  74. */
  75. leavesList.forEach(e -> {
  76. HuffmanNode current = e;
  77. StringBuilder code = new StringBuilder();
  78. do {
  79. if (current.parent != null && current == current.parent.left) { // 说明当前点是左边
  80. code.append(ZERO); //左边0
  81. } else {
  82. code.append(ONE);//左边1
  83. }
  84. current = current.parent;
  85. }while (current.parent != null); //父结点null是根结点
  86. code.reverse(); //因为我们是从叶子找回去的 ,所以最后需要将编码反转下
  87. leavesCodeMap.put(e.data, code.toString());
  88. });
  89. }
  90.  
  91. /**
  92. * 编码
  93. */
  94. public String encode(String msg) {
  95. char[] chars = msg.toCharArray();
  96. StringBuilder code = new StringBuilder();
  97. for (int i = 0; i < chars.length; i++) {
  98. code.append(leavesCodeMap.get(chars[i]));
  99. }
  100. return code.toString();
  101. }
  102.  
  103. /**
  104. * 解码
  105. */
  106. public String decode(String code) {
  107. char[] chars = code.toCharArray();
  108. Queue<Byte> queue = new ArrayDeque();
  109. for (int i = 0; i < chars.length; i++) {
  110. queue.add(Byte.parseByte(String.valueOf(chars[i])));
  111. }
  112. HuffmanNode current = root;
  113. StringBuilder sb = new StringBuilder();
  114. while (!queue.isEmpty() ){
  115. Byte aByte = queue.poll();
  116. if (aByte == ZERO) {
  117. current = current.left;
  118. }
  119. if (aByte == ONE) {
  120. current = current.right;
  121. }
  122. if (current.right == null && current.left == null) {
  123. sb.append(current.data);
  124. current = root;
  125. }
  126. }
  127. return sb.toString();
  128. }
  129.  
  130. /**
  131. * 结点 实现Comparable接口 方便使用优先队列(PriorityQueue)排序
  132. */
  133. private class HuffmanNode implements Comparable<HuffmanNode>{
  134.  
  135. Character data; //字符
  136. int weight; //权重
  137. HuffmanNode left;
  138. HuffmanNode right;
  139. HuffmanNode parent;
  140.  
  141. @Override
  142. public int compareTo(HuffmanNode o) {
  143. return this.weight - o.weight;
  144. }
  145. public HuffmanNode(Character data, int weight) {
  146. this.data = data;
  147. this.weight = weight;
  148. }
  149. }
  150. }

高级数据结构---赫(哈)夫曼树及java代码实现的更多相关文章

  1. hdu 2527:Safe Or Unsafe(数据结构,哈夫曼树,求WPL)

    Safe Or Unsafe Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)To ...

  2. C语言数据结构之哈夫曼树及哈夫曼编码的实现

    代码清单如下: #pragma once #include<stdio.h> #include"stdlib.h" #include <string.h> ...

  3. 【数据结构】赫夫曼树的实现和模拟压缩(C++)

    赫夫曼(Huffman)树,由发明它的人物命名,又称最优树,是一类带权路径最短的二叉树,主要用于数据压缩传输. 赫夫曼树的构造过程相对比较简单,要理解赫夫曼数,要先了解赫夫曼编码. 对一组出现频率不同 ...

  4. 哈夫曼树(三)之 Java详解

    前面分别通过C和C++实现了哈夫曼树,本章给出哈夫曼树的java版本. 目录 1. 哈夫曼树的介绍 2. 哈夫曼树的图文解析 3. 哈夫曼树的基本操作 4. 哈夫曼树的完整源码 转载请注明出处:htt ...

  5. Java 树结构实际应用 二(哈夫曼树和哈夫曼编码)

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

  6. Android版数据结构与算法(七):赫夫曼树

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 近期忙着新版本的开发,此外正在回顾C语言,大部分时间没放在数据结构与算法的整理上,所以更新有点慢了,不过既然写了就肯定尽力将这部分完全整理好分享出 ...

  7. Java数据结构和算法(四)赫夫曼树

    Java数据结构和算法(四)赫夫曼树 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 赫夫曼树又称为最优二叉树,赫夫曼树的一个 ...

  8. javascript实现数据结构: 树和二叉树的应用--最优二叉树(赫夫曼树),回溯法与树的遍历--求集合幂集及八皇后问题

    赫夫曼树及其应用 赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用. 最优二叉树(Huffman树) 1 基本概念 ① 结点路径:从树中一个结点到另一个结点的之间的分支 ...

  9. C#数据结构-赫夫曼树

    什么是赫夫曼树? 赫夫曼树(Huffman Tree)是指给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小.哈夫曼树(也称为最优二叉树)是带权路径长度最短的树,权值较大的结点 ...

随机推荐

  1. 硬不硬你说了算!35 张图解被问千百遍的 TCP 三次握手和四次挥手面试题

    每日一句英语学习,每天进步一点点: 前言 不管面试 Java .C/C++.Python 等开发岗位, TCP 的知识点可以说是的必问的了. 任 TCP 虐我千百遍,我仍待 TCP 如初恋. 遥想小林 ...

  2. POI2014 FAR-FarmCraft 树形DP+贪心

    题目链接 https://www.luogu.org/problem/P3574 题意 翻译其实已经很明确了 分析 这题一眼就是贪心啊,但贪心的方法要思索一下,首先是考虑先走时间多的子树,但不太现实, ...

  3. C# 快速开发框架搭建—开发工具介绍

    C# 快速开发框架搭建—开发工具介绍 一.VS2013,SQL SERVER R22008 以上两种工具如有不会者自行百度学习下. 二.动软代码生成器 对于经典的三层架构框架来说,使用动软代码生成器会 ...

  4. 数据库学习 day2 检索数据

    上一节我们介绍了什么是数据库,以及一些基本的数据库术语 这一课介绍使用SELECT语句从表中检索一个或多个数据列. 关键字(Keyword) 作为SQL组成部分的保留字.关键字不能用作表和列的名字(类 ...

  5. SI24R2F新一代2.4G超低功耗单发射有源RFID芯片 SI24R2E升级版智能充电安全管理方案首选

    目前全国有很多电动车因在充电时电池温度过高,而导致爆炸引起火灾的情况.作为国内RFID行业的推动者,动能世纪联合中科微向IOT应用领域推出新款大功率2.4G射频芯片,并针对电动车防盗.电动车充电桩市场 ...

  6. 当const放在function声明后

    #include <iostream> class MyClass { private: int counter; public: void Foo() { std::cout <& ...

  7. uni_app商城项目(完成)

    总结: 1.uni-app的跨段适配性,真的特别强,完成相关代码的书写,HbuilderX编辑器提供的打包十分方便. 2.开发小程序,H5等移动端开发, 比开发电脑端简单不少,但有时候坑也挺多的. 3 ...

  8. 计算机网络协议,TCP数据报的分析

    一.TCP协议的特点 TCP是面向连接的运输层协议:即应用程序在使用TCP协议通信之前,要先建立TCP连接,通信结束后必须释放已建立的TCP连接 每一条TCP连接只能有两个端点:即TCP是点对点(一对 ...

  9. Linux服务器架设篇,DNS服务器(一),基础知识

    一.端口 DNS监听端口 注意: DNS通常是以UDP协议来进行数据传输协议的,但是若没有办法查询到完整的信息是.DNS的daemon是named,它会启动TCP和UDP的53端口,所以启用DSN服务 ...

  10. javascript 入门(1)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta lang ...