哈夫曼树

既然是学习哈夫曼编码,我们首先需要知道什么是哈夫曼树:给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

哈夫曼编码

在日常计算机的使用中,我们一定会出现下面这种情况:假如给定a、b、c、d、e五个字符,它们在文本中出现的概率如下图所示:

字符 概率
a 0.12
b 0.40
c 0.15
d 0.05
e 0.25

我们现在要将文本编码成0/1序列从而使得计算机能够进行读取和计算。为了保证每个字符的独一性,所以我们给予不同的的字符以不同的编码。如果给每个字符赋予等长的编码的话,会使得平均的编码长度过长,影响计算时的性能,浪费计算机的资源(定长编码的缺点)。这时我们就想到了变长编码,理所当然的,给出现概率较大的字符赋予较短的编码,概率较小的字符赋予较长的编码,这样在计算的时候不就可以节省很多时间了吗?可这样我们又面临到了一个巨大的问题,我们来看下面这种情况,我们对字符进行编码:

字符 概率 编码
a 0.12 01
b 0.40 0
c 0.15 00
d 0.05 10
e 0.25 1

假设现在文本中的字符是bcd,转换之后的0/1序列为00010,可我们要在转换成文本的时候究竟是把第一位的0读作b还是把前两位的00读作c呢?为了解决这个问题,就又有了前缀码的概念。顾名思义,前缀码的含义就是任意字符的编码都不是其他字符编码的前缀。那么该如何形成前缀码呢?首先我们要构造一棵二叉树,指向左孩子的"边"记作0,指向右孩子的点记作“1”,叶子节点为代编码的字符,出现概率越大的字符离根的距离就越近。

字符 概率 编码
a 0.12 0100
b 0.40 1
c 0.15 0101
d 0.05 011
e 0.25 00

我们在前面提到:哈夫曼树的带权路径最小,所以有哈夫曼树构成的前缀码记作哈夫曼编码。哈夫曼作为已知的最佳无损压缩算法,满足前缀码的性质,可以即时解码。

哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)。

java代码实现

实现哈夫曼编码的主要思路为从指定的文件中读出文本,首先通过遍历获得各个字符出现的概率,根据出现概率的大小构造二叉树,在此基础上进行编码解码。

  • 首先来构造哈夫曼树的结点类,在结点类中有老生常谈的变量数据data、权重weight、指向左孩子的指针left、指向右孩子的指针right。除此之外,为了在构造了哈夫曼树之后容易对data进行编码,在这里我还使用了一个变量code来储存0/1字符。
public class Node<T> implements Comparable<Node<T>> {private T data;private double weight;private Node<T> left;private Node<T> right;String code;public Node(T data, double weight){this.data = data;this.weight = weight;this.code = "";}public T getData() {return data;}public void setData(T data) {this.data = data;}public double getWeight() {return weight;}public void setWeight(double weight) {this.weight = weight;}public Node<T> getLeft() {return left;}public void setLeft(Node<T> left) {this.left = left;}public Node<T> getRight() {return right;}public void setRight(Node<T> right) {this.right = right;}public String getCode(){return code;}public void setCode(String str){code = str;}@Overridepublic String toString(){return "data:"+this.data+";weight:"+this.weight+";code: "+this.code;}@Overridepublic int compareTo(Node<T> other) {if(other.getWeight() > this.getWeight()){return 1;}if(other.getWeight() < this.getWeight()){return -1;}return 0;}
}
  • 紧接着我们来构建哈夫曼树。

    • createTree方法返回一个结点,也就是根结点。首先把所有的nodes结点类都储存在一个List中,利用Collections的sort方法把结点按照权值的大小按照从大到小的顺序进行排列。然后把List中的倒数第二个元素设为左孩子,倒数第一个元素设为右孩子。这个时候要注意:它们的双亲结点为以左右孩子的权值的和作为权值的构成的新的结点。然后删去左右孩子结点,将形成的新结点加入的List中。直到List中只剩下一个结点,也就是根结点时为止。
    public Node<T> createTree(List<Node<T>> nodes) {while (nodes.size() > 1) {Collections.sort(nodes);Node<T> left = nodes.get(nodes.size() - 2);left.setCode(0 + "");Node<T> right = nodes.get(nodes.size() - 1);right.setCode(1 + "");Node<T> parent = new Node<T>(null, left.getWeight() + right.getWeight());parent.setLeft(left);parent.setRight(right);nodes.remove(left);nodes.remove(right);nodes.add(parent);}return nodes.get(0);}
    • 在构建哈夫曼树的类中还实现了一个广度遍历的方法,在遍历的时候,每遍历到左孩子,就把结点中的code变量加上“0”,这里的加不是简单的数学运算,而是字符串的叠加。每遍历到右孩子,就把结点中的code变量加上“1”,这样遍历过一遍后,叶子结点中的code储存的就是对应的哈夫曼编码值。
     public List<Node<T>> breadth(Node<T> root) {List<Node<T>> list = new ArrayList<Node<T>>();Queue<Node<T>> queue = new ArrayDeque<Node<T>>();if (root != null) {queue.offer(root);root.getLeft().setCode(root.getCode() + "0");root.getRight().setCode(root.getCode() + "1");}while (!queue.isEmpty()) {list.add(queue.peek());Node<T> node = queue.poll();if (node.getLeft() != null)node.getLeft().setCode(node.getCode() + "0");if (node.getRight() != null)node.getRight().setCode(node.getCode() + "1");if (node.getLeft() != null) {queue.offer(node.getLeft());}if (node.getRight() != null) {queue.offer(node.getRight());}}return list;}
  • 接下来我们首先从指定的文件中读取文本:
File file = new File("G:/usually/input/input1.txt");
  • 这里我们还需要构造一个类对读取的文本进行处理:chars数组储存的是文本中所有可能的字符,number数组中储存的是文本中所有字符出现的次数,这里我所申请的number数组的长度为27,为什么是27呢?因为在这里加上了空格。在num方法中利用双循环对字符进行计数,在这里不再过多赘述。
public class readtxt {char[] chars = new char[]{'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' '};int[] number = new int[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};public String txtString(File file){StringBuilder result = new StringBuilder();try{BufferedReader br = new BufferedReader(new FileReader(file));//构造一个BufferedReader类来读取文件String s = null;while((s = br.readLine())!=null){//使用readLine方法,一次读一行result.append(System.lineSeparator()+s);num(s);}br.close();}catch(Exception e){e.printStackTrace();}return result.toString();}public void num(String string){for(int i = 0;i<27;i++){int temp = 0;for(int j = 0;j<string.length();j++){if(string.charAt(j) == chars[i])temp++;}number[i] += temp;}}public int[] getNumber(){return number;}public char[] getChars(){return chars;}
}
  • 调用上面readtxt中的方法对文本进行处理,定义两个数组获得文本中出现的字符和字符出现的次数。
        readtxt read = new readtxt();String temp = read.txtString(file);int[] num = read.getNumber();char[] chars = read.getChars();
  • 利用一个循环把对应的data值和weight权重值构造成结点加入到list中。
for(int i = 0;i<27;i++){System.out.print(chars[i]+":"+num[i]+"   ");list.add(new Node<String>(chars[i]+"",num[i]));}
  • 构建哈夫曼树并得到根结点。
        HuffmanTree huffmanTree = new HuffmanTree();Node<String> root = huffmanTree.createTree(list);
  • 调用哈夫曼树中广度遍历方法,在建立两个新的list用来储存遍历之后的字符以及相对应的哈夫曼编码。
list2=huffmanTree.breadth(root);for(int i = 0;i<list2.size();i++){if(list2.get(i).getData()!=null) {list3.add(list2.get(i).getData());list4.add(list2.get(i).getCode());}}
  • 对从指定文本中读出的数据进行遍历,并与list3中的字符进行比较,如若相等,则转换为对应的变码。直至遍历结束,哈夫曼编码完成。
for(int i = 0;i<temp.length();i++){for(int j = 0;j<list3.size();j++){if(temp.charAt(i) == list3.get(j).charAt(0))result += list4.get(j);}}
  • 在解码的过程中我在这里选择了用一个list5把已编码完成的字符串分开来储存,然后对list5在进行遍历,从list5中取出一位元素temp2与list4中对应的编码进行比对,如果没有相同的,再从list5中读取一位加到temp2的后面,如果有,则清空temp2。每次找到相同的,就把对应的字符加到temp3的后面,直至整个list5遍历结束,temp3即为解码后的结果。
for(int i = 0;i<result.length();i++){list5.add(result.charAt(i)+"");}while (list5.size()>0){temp2 = temp2+"" +list5.get(0);list5.remove(0);for(int i=0;i<list4.size();i++){if (temp2.equals(list4.get(i))) {temp3 = temp3+""+list3.get(i);temp2 = "";}}}
  • 写入文件,大功告成!
File file2 =new File("G:/usually/input/input2.txt");Writer out =new FileWriter(file2);out.write(temp3);out.close();

  最后得到的结果:

完整代码

我有一个微信公众号,经常会分享一些Java技术相关的干货;如果你喜欢我的分享,可以用微信搜索“Java团长”或者“javatuanzhang”关注。

Java如何实现哈夫曼编码相关推荐

  1. java中哈夫曼编码所用的函数_数据结构(java语言描述)哈夫曼编码

    原理:哈夫曼编码是根据将已给出的权值作为叶子结点,生成一颗哈夫曼树,然后使得权重最小. 首先生成已给权重的所有的叶子结点,然后取所有节点中最小和次小的结点作为左右孩子生成一个哈夫曼树,计算出父节点的权 ...

  2. 2018.2.14 Java中的哈夫曼编码

    概念 哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种.Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造 ...

  3. 【Java数据结构与算法】第十二章 哈夫曼树和哈夫曼编码

    第十二章 哈夫曼树和哈夫曼编码 文章目录 第十二章 哈夫曼树和哈夫曼编码 一.哈夫曼树 1.基本术语 2.构建思路 3.代码实现 三.哈夫曼编码 1.引入 2.介绍 3.代码实现哈夫曼编码综合案例 一 ...

  4. 霍夫曼算法_霍夫曼编码算法

    霍夫曼算法 In this tutorial, we'll be discussing and implementing the Huffman Coding Algorithm in Java. 在 ...

  5. java 实现部门树_(java实现)哈夫曼(Huffman)树编码(自编压缩项目基础)

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

  6. 哈夫曼编码 译码java_基于Java的哈夫曼编码译码系统_报告毕业论文

    基于Java的哈夫曼编码译码系统_报告毕业论文 1课 程 设 计Java 与面向对象程序设计课程设计基于 Java 的哈夫曼编码译码系统1.问题描述和分工情况1.1 问题描述使用 Java 语言实现哈 ...

  7. 求解哈夫曼编码Java实现,用Java实现哈夫曼编码解决方法

    当前位置:我的异常网» J2SE » 用Java实现哈夫曼编码解决方法 用Java实现哈夫曼编码解决方法 www.myexceptions.net  网友分享于:2013-01-08  浏览:13次 ...

  8. 数据结构(哈夫曼树,哈夫曼编码)入门篇,JAVA实现

    什么是哈夫曼树 哈夫曼树就是一种最优判定树,举个例子,如下一个判断逻辑 if(s<60) g=1; else if(s<70) g=2 else if(s<80) g=3 else ...

  9. 使用哈夫曼编码实现数据的压缩和解压(java版)

    1.哈夫曼树 哈夫曼编码使用哈夫曼树的数据结构,哈夫曼树图解如下,即构造一个带权路径最小的数: 2.哈夫曼编码 使用哈夫曼树生成哈夫曼编码,已实现减少传输中数据的冗余:截取网络课程中的几张图来说明: ...

最新文章

  1. 【css】如何使页面压缩时文本内容不换行
  2. git分支指的是_git基础之分支含义
  3. 10万人的大场馆如何“画座位”?
  4. dotnet 将自动代码格式化机器人带入团队 GitLab 平台
  5. Java Executors(线程池)
  6. 主窗口刷新,子窗口闪烁
  7. oracle中job无法正常运行,如何排查
  8. Java中foreach 的用法以及注意事项
  9. CentOS6.3 编译安装LAMP(4):编译安装 PHP5.3.27
  10. web前端培训Node.js和JavaScript的关系
  11. 再生核希尔伯特空间:Hilbert Space与RKHS基础
  12. 【聚类算法】| Kmeans算法的Python实现(以西瓜数据集为例)
  13. asp毕业设计—— 基于asp+access的网上论坛设计与实现(毕业论文+程序源码)——网上论坛
  14. 关于小米手机用微信会重启的问题
  15. java单例模式(下)
  16. 如何使用 MATLAB 绘制小提琴图
  17. Setting up GlusterFS Volumes
  18. CIKM 2021 | 基于池化结构搜索的图分类
  19. VS插件_Supercharger_Auto Placeholders自动添加 用户+时间
  20. Java 读取数据写入word文档并导出

热门文章

  1. Django API接口文档使用说明
  2. layaBox配置vscode开发环境
  3. ps再制命令介绍以及案例展示再制命令的操作步骤
  4. 用python制作生日蛋糕_纯HTML5+CSS3制作生日蛋糕(代码易懂)
  5. ORA-12545 / TNS-12545
  6. pcDuino 刷系统-LiveSuit
  7. RabbitMQ 详细讲解
  8. Python批量获取基金数据
  9. 数据库题目汇总(下)
  10. 2.3 黑群晖驱动:开启nvme缓存、将nvme缓存作为存储盘 教程