20172323 2018-2019-1 《程序设计与数据结构》课堂测试报告
课程:《程序设计与数据结构》
班级: 1723
姓名: 王禹涵
学号: 20172323
实验教师:王志强老师
测试日期:2018年12月10日
必修/选修: 必修
1.测试内容
哈夫曼编码测试
设有字符集: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)把实验结果截图上传到云班课
2. 测试过程及结果
基本原理:
- 哈夫曼树:给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
路径: 树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。
路径长度:路径上的分枝数目称作路径长度。
树的路径长度:从树根到每一个结点的路径长度之和。
结点的带权路径长度:在一棵树中,如果其结点上附带有一个权值,通常把该结点的路径长度与该结点上的权值之积称为该结点的带权路径长度(weighted path length)
树的带权路径长度:如果树中每个叶子上都带有一个权值,则把树中所有叶子的带权路径长度之和称为树的带权路径长度。
具体实现:
- 第一步需要定义哈夫曼树的结点类HuffmanNode,其中包含了每个结点的携带的信息name,它的权重weight,指向它左右孩子的指针以及表示编码的String值code。同时为了方便进行排序,声明了Comparable接口
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.
- 基本的方法已经构造好了,接下来需要进行的是读取文件进行编码,在指定目录下添加一个txt文件,里面是需要进行编码的文字。运用
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
3. 代码链接
4. 测试过程中遇到的问题和解决过程
问题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)