课程:《程序设计与数据结构》
班级: 1723
姓名: 王禹涵
学号: 20172323
实验教师:王志强老师
测试日期:2018年12月10日
必修/选修: 必修java
哈夫曼编码测试
设有字符集:S={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}。
给定一个包含26个英文字母的文件,统计每一个字符出现的几率,根据计算的几率构造一颗哈夫曼树。
并完成对英文文件的编码和解码。
要求:
(1)准备一个包含26个英文字母的英文文件(能够不包含标点符号等),统计各个字符的几率
(2)构造哈夫曼树
(3)对英文文件进行编码,输出一个编码后的文件
(4)对编码文件进行解码,输出一个解码后的文件
(5)撰写博客记录实验的设计和实现过程,并将源代码传到码云
(6)把实验结果截图上传到云班课node
哈夫曼树:给定n个权值做为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。数组
路径: 树中一个结点到另外一个结点之间的分支构成这两个结点之间的路径。数据结构
路径长度:路径上的分枝数目称做路径长度。测试
树的路径长度:从树根到每个结点的路径长度之和。this
结点的带权路径长度:在一棵树中,若是其结点上附带有一个权值,一般把该结点的路径长度与该结点上的权值之积称为该结点的带权路径长度(weighted path length)编码
树的带权路径长度:若是树中每一个叶子上都带有一个权值,则把树中全部叶子的带权路径长度之和称为树的带权路径长度。.net
protected HuffmanNode left; protected HuffmanNode right; protected String name; protected double weight; protected String code; public HuffmanNode(String name, double weight){ this.name = name; this.weight = weight; code = ""; } public int compareTo(HuffmanNode node) { if (weight >= node.weight){ return 1; } else { return -1; } }
while(nodes.size() > 1){ Collections.sort(nodes); Node<T> left = nodes.get(nodes.size()-1); Node<T> right = nodes.get(nodes.size()-2); 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);
这里将全部的哈夫曼树的结点都存在了一个数组之中,判断数组1内是否有元素,进入while循环以后先将数组元素进行排序,而后取出数组中的最小元素和次小元素(便是数组中的末两位)分别做为左右孩子,两者之和做为父结点元素放入数组之中从新进行排序,直至数组中元素为一,哈夫曼树构造完成。紧接着是哈夫曼树的广度优先遍历方法设计
List<Node<T>> list = new ArrayList<Node<T>>(); Queue<Node<T>> queue = new ArrayDeque<Node<T>>(); if(root != null){ queue.offer(root); } while(!queue.isEmpty()){ list.add(queue.peek()); Node<T> node = queue.poll(); if(node.getLeft() != null){ queue.offer(node.getLeft()); } if(node.getRight() != null){ queue.offer(node.getRight()); } } return list;
基础的哈夫曼树构造好了,接着须要在此基础上进行哈夫曼编码。基本思路是,从根结点开始设二叉树的左子树编码为‘0’,右子树的编码为‘1’,依次编码下去直到叶结点,而后从根到每一个叶结点依次写出叶结点的编码--哈夫曼编码,具体实现就是在构造哈夫曼树的同时,加上以下代码指针
left.setCode("0"); right.setCode("1");
由于这里设置的是String型,因此要加上“”,表示字符串。同时遍历方法也要相应加上“0”,“1”。当遍历到左孩子时,加上1,遍历到右孩子时,加上0.
File file = new File("此处填写文件路径"); BufferedReader br = new BufferedReader(new FileReader(file));
读入文件内的信息。经过readline方法将信息成行读入,并从新拼接成字符串,逐个字符进行比对,统计出现的个数及几率并进行输出
List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); list.add("e"); list.add("f"); list.add("g"); list.add("h"); list.add("i"); list.add("j"); list.add("k"); list.add("l"); list.add("m"); list.add("n"); list.add("o"); list.add("p"); list.add("q"); list.add("r"); list.add("s"); list.add("t"); list.add("u"); list.add("v"); list.add("w"); list.add("x"); list.add("y"); list.add("z"); list.add(" "); 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}; File file = new File("C:\\Users\\10673\\Desktop\\input.txt"); BufferedReader br = new BufferedReader(new FileReader(file)); String s; String message = ""; while((s = br.readLine()) != null){ message += s; } String[] result = message.split(""); for (int n = 0;n < result.length; n++){ for (int i = 0; i < 27; i++){ if (result[n].equals(list.get(i))){ number[i] += 1; } } } List<HuffmanNode> nodeList = new ArrayList<HuffmanNode>(); DecimalFormat df = new DecimalFormat( "0.0000000"); double wei; double sum = result.length; for(int i = 0;i<27;i++){ wei = ((double) number[i]/sum); System.out.println(list.get(i) + "出现" + number[i] + "次,几率为" + df.format(wei)); nodeList.add(new HuffmanNode(list.get(i),number[i])); } Collections.sort(nodeList); HuffmanTree huffmanTree = new HuffmanTree(); HuffmanNode node = huffmanTree.createTree(nodeList); List<HuffmanNode> inlist = new ArrayList<HuffmanNode>(); inlist = huffmanTree.breadth(node);
以上代码并非特别的复杂,我构造了一个字符列表,用以与获得的字符串进行比对,当某个字符比对成功以后,相应位置的数组的值加一,这样就能够统计到每一个字符出现的次数。再而后计算获得每一个字符出现的几率,将这些字符及其权重存储在HuffmanNode的列表中,经过此就能够调用哈夫曼树的构造方法。剩下的几个方法,就是将哈夫曼树的结点逐个进行编码解码,并输出到指定的文件之中
运行结果如图所示
[](https://img2018.cnblogs.com/blog/1332964/201812/1332964-20181211214511138-1894899771.png
问题1:运用除法计算字符出现的几率时,运算结果所有为0,如图
问题1解决方案:最开始觉得是显示的只有小数点后一位,因此不少数据比较小,四舍五入以后就只剩下0.0,查阅资料从新控制double类型小数点后位数的方法
DecimalFormat df = new DecimalFormat( "0.00"); //设置double类型小数点后位数格式 double d1 = 2.1; System.out.println(df.format(d1)); //将输出2.10
输出以后发现几率所有都为0.这里运用到的方法是number[i]/sum
,sum是总的字符个数,number[i]依次是每一个字符的出现次数。
问题就出在这个除法上,由于这是两个整型数相除,因此获得的依然会是一个整型数,即使再把它转换成double类型,最终出来的也是0.0,因此应该在相除以前就将两个除数转换为double类型,这样得出的结果就是正确的
- [哈夫曼树的java实现](https://blog.csdn.net/jdhanhua/article/details/6621026)