当前位置:   article > 正文

课程设计---哈夫曼树的编码与解码(Java详解)_哈夫曼编码译码器java

哈夫曼编码译码器java

目录

一.设计任务&&要求:

二.方案设计报告:

2.1 哈夫曼树编码&译码的设计原理:

2.3设计目的:

2.3设计的主要过程:

2.4程序方法清单:

三.整体实现源码:

四.运行结果展示:

五.总结与反思:


一.设计任务&&要求:

题目要求:测试数据是一段任意的英文,也可以是一段完整的中文,采用哈夫曼算法进行编码,可输出对应的字符编码的解码

哈夫曼编码是一种最优变长码,即带权路径最小。这种编码有很强的应用背景,是数据压缩中的一个重要理论依据。对输入的一串文字符号实现哈夫曼编码,再对哈夫曼编码生成的代码串进行译码,输出字符串。要求完成以下功能:

1.针对给定的字符串,建立哈夫曼树。

2.生成哈夫曼编码。

3.对编码字符串译码

二.方案设计报告:

2.1 哈夫曼树编码&译码的设计原理:

  • 哈夫曼编译码器的主要功能是先建立哈夫曼树,然后利用建好的哈夫曼树生成哈夫曼编码后进行译码。在数据通信中,通常需要将传送文字转换成由二进制字符0,1组成的二进制串,称之为编码。构建一个哈夫曼树,设定哈夫曼树中的左分支为0,右分支代表1,则从根结点到每个叶子节点所经过的路径组成的0和1的序列便为该节点对应字符的编码,称之为哈夫曼编码。最简单的二进制编码方式是等长编码。若采用不等长编码,让出现频率高的字符具有较短的编码,让出现频率低的字符具有较长的编码,这样可以有效缩短传送文字的总长度。哈夫曼树则是用于构造使编码总长最短,最节省空间成本的编码方案。
  • 2.3设计目的:

  • (1) 巩固和加深对数据结构课程所学知识的理解,了解并掌握数据结构与算法的设计方法;
    (2) 初步掌握软件开发过程的问题分析、系统设计、程序编码、测试等基本方法和技能;
    (3) 提高综合运用所学的理论知识和方法,独立分析和解决问题的能力;
    (4) 训练用系统的观点和软件开发一般规范进行软件开发,培养软件工作者所应具备的科学的工作方法和作风;
    (5) 培养查阅资料,独立思考问题的能力。

2.3设计的主要过程:

     1.哈夫曼树叶子节点的创建

叶子节点需要存储字符,及其出现的频率,指向左右子树的指针和将来字符所编码成的二进制数字。这里用一个静态内部来来初始化树的叶子节点:

  1. //用一个静态内部类来初始化树的节点
  2. static class Node{
  3. char ch; //记录字符
  4. int freq; //统计每个字符出现的频次
  5. Node left;
  6. Node right;
  7. String code; //编码
  8. public Node(char ch) {
  9. this.ch = ch;
  10. }
  11. public Node(int freq, Node left, Node right) {
  12. this.freq = freq;
  13. this.left = left;
  14. this.right = right;
  15. }
  16. //判断是否是叶子节点->哈夫曼树是满二叉树
  17. boolean isLeaf(){
  18. return left == null;
  19. }
  20. public char getCh() {
  21. return ch;
  22. }
  23. public void setCh(char ch) {
  24. this.ch = ch;
  25. }
  26. public int getFreq() {
  27. return freq;
  28. }
  29. public void setFreq(int freq) {
  30. this.freq = freq;
  31. }
  32. //重写的toString 方法只需要打印字符和其对应的频次即可
  33. @Override
  34. public String toString() {
  35. return "Node{" +
  36. "ch=" + ch +
  37. ", freq=" + freq +
  38. '}';
  39. }
  40. }

      2.构建哈夫曼树

 构建过程:首先要统计每个字符出现的频率①.将统计了出现频率的字符,放入优先级队列中,利用优先级队列的特点,将字符按照出现的频率从小到大排序②.每次出队两个频次最低的元素,给它们找一个父亲节点③.将父亲节点放入队列中,重复②~③两个步骤④.当队列中只剩一个元素时,哈夫曼树就构建完了 

  1. //构造哈夫曼树
  2. //->由于这里是一个自定义的类,我们需要传入比较器,按照节点的频次进行比较
  3. PriorityQueue<Node> q = new PriorityQueue<>(
  4. //通过Comparator 的方法来获得Node节点的其中一个属性
  5. Comparator.comparingInt(Node::getFreq)
  6. );
  7. for(Node node : hash.values()){
  8. q.offer(node);
  9. }
  10. while(q.size() >= 2){
  11. Node x = q.poll();
  12. Node y = q.poll();
  13. int freq = x.freq + y.freq;
  14. q.offer(new Node(freq,x,y));
  15. }

       3.哈夫曼编码

通过将每个叶子节点保存好的字符编码利用StringBuilder中的append()方法拼接起来后返回即可

  1. //编码操作:
  2. public String encode(){
  3. char[] chars = str.toCharArray();
  4. StringBuilder sb = new StringBuilder();
  5. for(char c : chars){
  6. sb.append(hash.get(c).code);
  7. }
  8. return sb.toString();
  9. }

     4.哈夫曼译码

 从根节点开始,寻找数字对应的字符编码,如果是0向右走,如果是数字1向左走,如果没有走到头(一个字符的编码结尾),每一步数字的索引cur++,每找到一个编码字符,在将node重置为根节点,接着重个节点开始继续往下寻找,一直找到字符串末尾即可

  1. /**
  2. * 从根节点开始,寻找数字对应的字符
  3. * 数字是0 向右走,数字是1 向左走
  4. * 如果没有走到头,每一步数字的索引 cur++
  5. * 走到头就可以 找到编码字符,再将node 重置为根节点
  6. * @param str
  7. * @return
  8. */
  9. //解码操作:
  10. public String decode(String str){
  11. char[] chars = str.toCharArray();
  12. StringBuilder sb = new StringBuilder();
  13. int cur = 0;
  14. Node node = root;
  15. while(cur < chars.length){
  16. if(!node.isLeaf()){//非叶子节点
  17. if(chars[cur] == '0'){//向左走
  18. node = node.left;
  19. }else if(chars[cur] == '1'){//向右走
  20. node =node.right;
  21. }
  22. //每走完一步 cur++;
  23. cur++;
  24. if(node.isLeaf()){
  25. sb.append(node.ch);
  26. //每找到一个叶子节点,就重置后再次查找,直到遍历完整个数组
  27. node = root;
  28. }
  29. }
  30. }
  31. return sb.toString();
  32. }
  • 大致模块图:

设计流程图:

2.4程序方法清单:

①.构造哈夫曼树:public HuffmanTree(){

}//这里我选择在函数的构造方法中将哈夫曼树给先构造完

②.编码:public String encode(){};

③.解码:pulbic String decode(){};

④.找到编码,并计算其对应的bit位:private int findCode(Node node,StringBuilder code){};

⑤.打印菜单:menu(){};

⑥.测试函数:main(){};

模块展示:

三.整体实现源码:

  1. import java.util.*;
  2. public class HuffmanTree {
  3. //用一个静态内部类来初始化树的节点
  4. static class Node{
  5. char ch; //记录字符
  6. int freq; //统计每个字符出现的频次
  7. Node left;
  8. Node right;
  9. String code; //编码
  10. public Node(char ch) {
  11. this.ch = ch;
  12. }
  13. public Node(int freq, Node left, Node right) {
  14. this.freq = freq;
  15. this.left = left;
  16. this.right = right;
  17. }
  18. //判断是否是叶子节点->哈夫曼树是满二叉树
  19. boolean isLeaf(){
  20. return left == null;
  21. }
  22. public char getCh() {
  23. return ch;
  24. }
  25. public void setCh(char ch) {
  26. this.ch = ch;
  27. }
  28. public int getFreq() {
  29. return freq;
  30. }
  31. public void setFreq(int freq) {
  32. this.freq = freq;
  33. }
  34. //重写的toString 方法只需要打印字符和其对应的频次即可
  35. @Override
  36. public String toString() {
  37. return "Node{" +
  38. "ch=" + ch +
  39. ", freq=" + freq +
  40. '}';
  41. }
  42. }
  43. String str;
  44. Node root;
  45. Map<Character,Node> hash = new HashMap<>();
  46. public HuffmanTree(){
  47. }
  48. public HuffmanTree(String str){
  49. this.str = str;
  50. //统计字符出现的频次
  51. char[] chars = str.toCharArray();
  52. for(char ch : chars){
  53. if(!hash.containsKey(ch)){
  54. hash.put(ch,new Node(ch));
  55. }
  56. Node node = hash.get(ch);
  57. node.freq++;
  58. }
  59. for(Node node : hash.values()){
  60. System.out.println(node);
  61. }
  62. //构造哈夫曼树
  63. //->由于这里是一个自定义的类,我们需要传入比较器,按照节点的频次进行比较
  64. PriorityQueue<Node> q = new PriorityQueue<>(
  65. //通过Comparator 的方法来获得Node节点的其中一个属性
  66. Comparator.comparingInt(Node::getFreq)
  67. );
  68. for(Node node : hash.values()){
  69. q.offer(node);
  70. }
  71. while(q.size() >= 2){
  72. Node x = q.poll();
  73. Node y = q.poll();
  74. int freq = x.freq + y.freq;
  75. q.offer(new Node(freq,x,y));
  76. }
  77. root = q.poll();
  78. //System.out.println(root);
  79. //计算每个字符的编码 以及其一共包含的bit位
  80. System.out.println("输出编码信息:");
  81. int sum = findCode(root,new StringBuilder());
  82. for(Node node : hash.values()){
  83. //打印节点及其编码信息
  84. System.out.println(node + " " + node.code);
  85. }
  86. System.out.println("总共占有的bit位是:" + sum);
  87. }
  88. //找到编码,并计算其对应的bit位
  89. private int findCode(Node node,StringBuilder code){
  90. int sum = 0;
  91. if(node.isLeaf()){
  92. //找到编码 并计算字符串编码后所占的bits
  93. node.code = code.toString();
  94. sum = node.freq * code.length();
  95. }else{
  96. sum += findCode(node.left,code.append("0"));
  97. code.deleteCharAt(code.length() - 1);
  98. sum += findCode(node.right,code.append("1"));
  99. code.deleteCharAt(code.length() - 1);
  100. }
  101. return sum;
  102. }
  103. //编码操作:
  104. public String encode(){
  105. char[] chars = str.toCharArray();
  106. StringBuilder sb = new StringBuilder();
  107. for(char c : chars){
  108. sb.append(hash.get(c).code);
  109. }
  110. return sb.toString();
  111. }
  112. /**
  113. * 从根节点开始,寻找数字对应的字符
  114. * 数字是0 向右走,数字是1 向左走
  115. * 如果没有走到头,每一步数字的索引 cur++
  116. * 走到头就可以 找到编码字符,再将node 重置为根节点
  117. * @param str
  118. * @return
  119. */
  120. //解码操作:
  121. public String decode(String str){
  122. char[] chars = str.toCharArray();
  123. StringBuilder sb = new StringBuilder();
  124. int cur = 0;
  125. Node node = root;
  126. while(cur < chars.length){
  127. if(!node.isLeaf()){//非叶子节点
  128. if(chars[cur] == '0'){//向左走
  129. node = node.left;
  130. }else if(chars[cur] == '1'){//向右走
  131. node =node.right;
  132. }
  133. //每走完一步 cur++;
  134. cur++;
  135. if(node.isLeaf()){
  136. sb.append(node.ch);
  137. //每找到一个叶子节点,就重置后再次查找,直到遍历完整个数组
  138. node = root;
  139. }
  140. }
  141. }
  142. return sb.toString();
  143. }
  144. static final int all_block_nums = 100;
  145. public static void memu() throws InterruptedException {
  146. //菜单加载页面
  147. for(int i = 0;i < all_block_nums;i++){
  148. System.out.printf("\r[%d%%]>",i*100/(all_block_nums-1));
  149. for(int j = 1;j <= i*20/(all_block_nums);j++){
  150. System.out.print("▉");
  151. Thread.sleep(2);
  152. }
  153. }
  154. System.out.println();
  155. System.out.println("-------------------------------------------");
  156. System.out.println("---------- ------------");
  157. System.out.println("-------- 欢迎使用哈夫曼编码 ----------");
  158. System.out.println("--------- 1.编码与解码 ---------");
  159. System.out.println("---------- 0.退出 ----------");
  160. System.out.println("-------------------------------------------");
  161. }
  162. public static void main(String[] args) throws InterruptedException {
  163. Scanner sc = new Scanner(System.in);
  164. while(true){
  165. memu();
  166. String str = "0000";
  167. System.out.println("请选择:");
  168. int input = sc.nextInt();
  169. switch (input){
  170. case 0:
  171. System.out.println("你选择了退出程序~~~");
  172. break;
  173. case 1 :
  174. System.out.println("你选择了编码与解码");
  175. System.out.println("请输入要编码的字符串:");
  176. String in = sc.next();
  177. HuffmanTree huffmanTree = new HuffmanTree(in);
  178. str = huffmanTree.encode();
  179. System.out.println("编码后的字符串为:");
  180. System.out.println(str);
  181. System.out.println("将刚才编码好的字符串进行解码:");
  182. String cur = huffmanTree.decode(str);
  183. System.out.println("解码后的字符串:");
  184. System.out.println(cur);
  185. }
  186. if(input == 0) break;
  187. }
  188. }
  189. }

四.运行结果展示:

五.总结与反思:

这次课程设计的心得体会通过实践我的收获如下:
① 巩固和加深了对数据结构的理解,提高综合运用本课程所学知识的能力。
② 培养了我选用参考书,查阅手册及文献资料的能力。培养独立思考,深入研究,分析问题、解决问题的能力。
③ 通过实际编译系统的分析设计、编程调试,掌握应用软件的分析方法和工程设计方法。
④ 通过课程设计,培养了我严肃认真的工作作风,逐步建立正确的生产观念、经济观念和全局观念。

通过本次数据结构的课设计,我学习了很多在上课没懂的知识,并对求哈夫曼树及哈夫曼编码/译码的算法有了更加深刻的了解,更巩固了课堂中学习有关于哈夫曼编码的知识,真正学会一种算法了。当求解一个算法时,不是拿到问题就不加思索地做,而是首先要先对它有个大概的了解,接着再详细地分析每一步怎么做,无论自己以前是否有处理过相似的问题,只要按照以上的步骤,必会顺利地做出来。

结语: 写博客不仅仅是为了分享学习经历,同时这也有利于我巩固知识点,总结该知识点,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进。同时也希望读者们不吝啬你们的点赞+收藏+关注,你们的鼓励是我创作的最大动力!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/777301
推荐阅读
相关标签
  

闽ICP备14008679号