20182301 2019-2020-1 《数据结构与面向对象程序设计》哈夫曼实验报告
课程:《程序设计与数据结构》
班级: 1823
姓名: 赵沛凝
学号:20182301
实验教师:王志强
实验日期:2019年11月18日
必修/选修: 必修
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个英文字母的文件,统计每个字符出现的概率,根据计算的概率构造一颗哈夫曼树。
并完成对英文文件的编码和解码。
要求:- 准备一个包含26个英文字母的英文文件(可以不包含标点符号等),统计各个字符的概率
- 构造哈夫曼树
- 对英文文件进行编码,输出一个编码后的文件
- 对编码文件进行解码,输出一个解码后的文件
- 撰写博客记录实验的设计和实现过程,并将源代码传到码云
- 把实验结果截图上传到云班课
2. 实验过程及结果
- 定义:
- 哈夫曼树(霍夫曼树)又称为最优树.
- 路径和路径长度
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长 - 结点的权及带权路径长度
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。 - 树的带权路径长度
树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL
- 路径和路径长度
- 第一步,了解文件读入和文件写入的方法
//读取文件
File file = new File("E:\\idea\\week_12s\\input.txt");
Scanner scan = new Scanner(file);//Scanner的新用法
String s =scan.nextLine();//读取出来字符串
//写入文件
File file2 = new File("E:\\idea\\week_12s\\put.txt");
FileWriter fileWriter1 = new FileWriter(file1);
fileWriter1.write(result2);
- 第二步,打印频率
System.out.println("打印各字母出现频率:");
for (int i = 0; i < array.length; i++) {
System.out.print((char) ('a' + i) + ":" + (double) array[i] / s.length() + "\n");
}//把26个字母的概率都打出来
- 第三步:将数组转换为哈夫曼结点
HuffmanTreeNode[] huffmanTreeNodes = new HuffmanTreeNode[array.length];//构建哈夫曼树的结点
for (int i = 0; i < array.length; i++) {
huffmanTreeNodes[i] = new HuffmanTreeNode(array[i], (char) ('a' + i), null, null, null);
}
- 第四步:构建哈夫曼树
package com.company;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Stack;
public class HuffmanTree {
HuffmanTreeNode root; // 根结点
private String[] codes = new String[26];
public HuffmanTree(HuffmanTreeNode[] a) throws EmptyCollectionException {
HuffmanTreeNode parent = null;
ArrayHeap<HuffmanTreeNode> heap;
// 建立数组a对应的最小堆
heap = new ArrayHeap<>();
for(int i=0; i<a.length; i++) {
heap.addElement(a[i]);
}
for(int i=0; i<a.length-1; i++) {
HuffmanTreeNode left = heap.removeMin(); // 最小节点是左孩子
HuffmanTreeNode right = heap.removeMin();
parent = new HuffmanTreeNode(left.weight+right.weight,' ',left,right,null);
left.parent = parent;
right.parent = parent;
heap.addElement(parent);
}
root = parent;
}
public String printTree() throws EmptyCollectionException {
UnorderedListADT<HuffmanTreeNode> nodes =
new ArrayUnorderedList();
UnorderedListADT<Integer> levelList =
new ArrayUnorderedList<Integer>();
HuffmanTreeNode current;
String result = "";
int printDepth = this.getHeight()-1;
int possibleNodes = (int)Math.pow(2, printDepth + 1);
int countNodes = 0;
nodes.addToRear(root);
Integer currentLevel = 0;
Integer previousLevel = -1;
levelList.addToRear(currentLevel);
while (countNodes < possibleNodes)
{
countNodes = countNodes + 1;
current = nodes.removeFirst();
currentLevel = levelList.removeFirst();
if (currentLevel > previousLevel)
{
result = result + "\n\n";
previousLevel = currentLevel;
for (int j = 0; j < ((Math.pow(2, (printDepth - currentLevel))) - 1); j++)
result = result + " ";
}
else
{
for (int i = 0; i < ((Math.pow(2, (printDepth - currentLevel + 1)) - 1)) ; i++)
{
result = result + " ";
}
}
if (current != null)
{
result = result + current.weight;
nodes.addToRear(current.left);
levelList.addToRear(currentLevel + 1);
nodes.addToRear(current.right);
levelList.addToRear(currentLevel + 1);
}
else {
nodes.addToRear(null);
levelList.addToRear(currentLevel + 1);
nodes.addToRear(null);
levelList.addToRear(currentLevel + 1);
result = result + " ";
}
}
return result;
}
private int getHeight() {
return height(root);
}
private int height(HuffmanTreeNode node)
{
if(node==null){
return 0;
}
else {
int leftTreeHeight = height(node.left);
int rightTreeHeight= height(node.right);
return leftTreeHeight>rightTreeHeight ? (leftTreeHeight+1):(rightTreeHeight+1);
}
}
protected void inOrder( HuffmanTreeNode node,
ArrayList<HuffmanTreeNode> tempList)
{
if (node != null)
{
inOrder(node.left, tempList);
if (node.element!=' ')
tempList.add(node);
inOrder(node.right, tempList);
}
}
public String[] getEncoding() {
ArrayList<HuffmanTreeNode> arrayList = new ArrayList();
inOrder(root,arrayList);
for (int i=0;i<arrayList.size();i++)
{
HuffmanTreeNode node = arrayList.get(i);
String result ="";
int x = node.element-'a';
Stack stack = new Stack();
while (node!=root)
{
if (node==node.parent.left)
stack.push(0);
if (node==node.parent.right)
stack.push(1);
node=node.parent;
}
while (!stack.isEmpty())
{
result +=stack.pop();
}
codes[x] = result;
}
return codes;
}
public HuffmanTreeNode getRoot() {
return root;
}
}
- 第五步:进行加密
//进行编码:二进制加法
String result = "";
for (int i = 0; i < s.length(); i++) {
int x = s.charAt(i) - 'a';
result += codes[x];
}
System.out.println("编码结果:" + result);
- 第六步:解码
//进行解码
String result2 = "";
for (int i = 0; i < s1.length(); i++) {
if (s1.charAt(i) == '0') {
if (huffmanTreeNode.left != null) {
huffmanTreeNode = huffmanTreeNode.left;
}
} else {
if (s1.charAt(i) == '1') {
if (huffmanTreeNode.right != null) {
huffmanTreeNode = huffmanTreeNode.right;
}
}
}
if (huffmanTreeNode.left == null && huffmanTreeNode.right == null) {
result2 += huffmanTreeNode.element;//把一个个字母加起来
huffmanTreeNode = huffmanTree.getRoot();//移到上一个结点
}
}
3. 实验过程中遇到的问题和解决过程
- 问题1:为什么写入文件,但是文件不出现内容呢?
- 问题1解决方案:
- 可能是因为文件内容存于缓冲区中,不能写入,读取文件时可以从缓冲区中读取,于是我修改了写入的方法
//类似
FileOutputStream fop = null;
File file;
String content = "This is the text content";
try {
file = new File("c:/newfile.txt");
fop = new FileOutputStream(file);
// if file doesnt exists, then create it
if (!file.exists()) {
file.createNewFile();
}
// get the content in bytes
byte[] contentInBytes = content.getBytes();
fop.write(contentInBytes);
fop.flush();
fop.close();
System.out.println("Done");
- 问题2:我在学习中又看到解压加压的方法,学习一下
- 问题2解决办法:
- 写了压缩的程序
package com.company;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class Test
{
static String filePath = "E:/idea/week_12s/zip";//需要压缩的文件夹完整路径
static String fileName = "zip";//需要压缩的文件夹名
static String outPath = "E:/idea/week_12s/zip/test.zip";//压缩完成后保存为Test.zip文件,名字随意
public static void main(String args[]) throws Exception
{
OutputStream is = new FileOutputStream(outPath);//创建Test.zip文件
CheckedOutputStream cos = new CheckedOutputStream(is, new CRC32());//检查输出流,采用CRC32算法,保证文件的一致性
ZipOutputStream zos = new ZipOutputStream(cos);//创建zip文件的输出流
zos.setComment("GBK");//设置编码,防止中文乱码
File file = new File(filePath);//需要压缩的文件或文件夹对象
ZipFile(zos,file);//压缩文件的具体实现函数
zos.close();
cos.close();
is.close();
System.out.println("压缩完成");
}
//递归,获取需要压缩的文件夹下面的所有子文件,然后创建对应目录与文件,对文件进行压缩
public static void ZipFile(ZipOutputStream zos,File file) throws Exception
{
if(file.isDirectory())
{
//创建压缩文件的目录结构
zos.putNextEntry(new ZipEntry(file.getPath().substring(file.getPath().indexOf(fileName))+File.separator));
for(File f : file.listFiles())
{
ZipFile(zos,f);
}
}
else
{
//打印输出正在压缩的文件
System.out.println("正在压缩文件:"+file.getName());
//创建压缩文件
zos.putNextEntry(new ZipEntry(file.getPath().substring(file.getPath().indexOf(fileName))));
//用字节方式读取源文件
InputStream is = new FileInputStream(file.getPath());
//创建一个缓存区
BufferedInputStream bis = new BufferedInputStream(is);
//字节数组,每次读取1024个字节
byte [] b = new byte[1024];
//循环读取,边读边写
while(bis.read(b)!=-1)
{
zos.write(b);//写入压缩文件
}
//关闭流
bis.close();
is.close();
}
}
}
- 结果:压缩成功,解压也该也大同小异,就不在这里缀述了
代码链接
其他(感悟、思考等)
到现在,我已经学习了很多树了,逐渐有了树的思维,以后也会多多运用的。