172322 2018-2019-1 《程序设计与数据结构》哈夫曼编码测试报告
- 课程:《程序设计与数据结构》
- 班级: 1723
- 姓名: 张昊然
- 学号:20172322
- 教师:王志强
- 助教:张之睿/张师瑜
- 编码测试日期:2018年11月19日
- 必修/选修: 必修
哈夫曼树
- 在计算机数据处理中,霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而达到无损压缩数据的目的。
- 例如,在英文中,e的出现机率最高,而z的出现概率则最低。当利用霍夫曼编码对一篇英文进行压缩时,e极有可能用一个比特来表示,而z则可能花去25个比特(不是26)。用普通的表示方法时,每个英文字母均占用一个字节,即8个比特。二者相比,e使用了一般编码的1/8的长度,z则使用了3倍多。倘若我们能实现对于英文中各个字母出现概率的较准确的估算,就可以大幅度提高无损压缩的比例。
- 霍夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的路径长度是从树根到每一结点的路径长度之和,记为WPL=(W1XL1+W2XL2+W3XL3+...+WnXLn),N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。可以证明霍夫曼树的WPL是最小的。
哈夫曼树的应用
- 对需要编码的数据进行两遍扫描:第一遍统计原数据中各字符出现的频率,利用得到的频率值创建哈夫曼树,并必须把树的信息保存起来,即把字符0-255(2^8=256)的频率值以2-4BYTES的长度顺序存储起来,(用4Bytes的长度存储频率值,频率值的表示范围为0至2^32-1,这已足够表示大文件中字符出现的频率了)以便解压时创建同样的哈夫曼树进行解压;第二遍则根据第一遍扫描得到的哈夫曼树进行编码,并把编码后得到的码字存储起来。
哈弗曼树的实现
实现时参考了博客
java创建哈夫曼树和实现哈夫曼编码和坤澎的博客——java实现哈夫曼编码,在读懂代码的同时,在一些地方加上了自己的备注- 首先建立哈夫曼树的结点类
HuffmanNode
利用泛型实现结点,定义左子树,右子树,权值和编码。
// 哈夫曼节点类
public class HuffmanNode<T> implements Comparable<HuffmanNode<T>> {
private T data; // 数据
private double weight; // 权重
private HuffmanNode<T> left;
private HuffmanNode<T> right;
String code; // 编码
public HuffmanNode(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 HuffmanNode<T> getLeft() {
return left;
}
public void setLeft(HuffmanNode<T> left) {
this.left = left;
}
public HuffmanNode<T> getRight() {
return right;
}
public void setRight(HuffmanNode<T> right) {
this.right = right;
}
public String getCode(){
return code;
}
public void setCode(String str){
code = str;
}
@Override
public String toString(){
return null;
}
@Override
public int compareTo(HuffmanNode<T> other) {
if(other.getWeight() > this.getWeight()){
return 1;
}
if(other.getWeight() < this.getWeight()){
return -1;
}
return 0;
}
}
- 编写
ReadTxt
类读取txt文件中的字母,用于将字母转化成哈夫曼编码。
// 读取文件类
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[] nu = new int[26];
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){
// 26个字母加一个空格
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;
}
}
- 编写
HuffmanCoding
类将读取的字母转化成为哈夫曼编码,并且规定左子树为0,右子树为1
public class HuffmanCoding<T> {
public HuffmanNode<T> createTree(List<HuffmanNode<T>> nodes) {
while (nodes.size() > 1) {
Collections.sort(nodes);
HuffmanNode<T> left = nodes.get(nodes.size() - 2);
left.setCode(0 + ""); // 左子树为否(0)
HuffmanNode<T> right = nodes.get(nodes.size() - 1);
right.setCode(1 + ""); // 右子树为是(1)
HuffmanNode<T> parent = new HuffmanNode<>(null, left.getWeight() + right.getWeight()); // 双亲结点的权值为左右孩子相加
parent.setLeft(left);
parent.setRight(right);
nodes.remove(left);
nodes.remove(right);
nodes.add(parent);
}
return nodes.get(0); // 返回根结点
}
// 广度优先遍历以赋值
public List<HuffmanNode<T>> BFS(HuffmanNode<T> root) {
List<HuffmanNode<T>> list = new ArrayList<>();
Queue<HuffmanNode<T>> queue = new ArrayDeque<>();
if (root != null) {
// 将元素入队列
queue.offer(root);
root.getLeft().setCode(root.getCode() + "0");
root.getRight().setCode(root.getCode() + "1");
}
// 转化为0和1
while (!queue.isEmpty()) {
list.add(queue.peek());
HuffmanNode<T> node = queue.poll(); // 获取头结点
if (node.getLeft() != null) {
node.getLeft().setCode(node.getCode() + "0"); // 左为0
}
if (node.getRight() != null) {
node.getRight().setCode(node.getCode() + "1"); // 右为1
}
if (node.getLeft() != null) {
queue.offer(node.getLeft());
}
if (node.getRight() != null) {
queue.offer(node.getRight());
}
}
return list;
}
}
- 最后编写实现类
Huffman
从input.txt
中读取信息,转化为哈夫曼编码后将哈夫曼编码放入output.txt
中后完成测试。
public class Huffman {
public static void main(String[] args) throws IOException {
List<HuffmanNode<String>> list = new ArrayList<>();
List<HuffmanNode<String>> list2;
List<String> list3 = new ArrayList<>();
List<String> list4 = new ArrayList<>();
List<String> list5 = new ArrayList<>();
String temp2 = "",temp3 = "";
String result="";
double num2 = 0;
File file = new File("C:\\Users\\机械革命.000\\Desktop\\程序设计\\大二上\\哈夫曼\\input.txt");
ReadTxt read = new ReadTxt();
String temp = read.txtString(file);
System.out.println("读取的文件是:" + temp);
int[] num = read.getNumber(); // 存放出现次数的数组
char[] chars = read.getChars(); // 存放元素的数组
for(int i = 0;i<27;i++){
System.out.print(chars[i]+":"+num[i]+" ");
list.add(new HuffmanNode<>(chars[i]+"",num[i]));
}
Collections.sort(list); // 按照自然顺序排序
System.out.println();
HuffmanCoding huffmanTree = new HuffmanCoding();
HuffmanNode<String> root = huffmanTree.createTree(list);
list2 = huffmanTree.BFS(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());
}
}
for(int i = 0;i<list2.size();i++){
num2 += list2.get(i).getWeight();
}
Collections.sort(list3);
System.out.println("出现的概率中第一个代表空格。");
for(int i = 0;i<list3.size();i++){
System.out.print(list3.get(i) + "出现的概率为:" + list2.get(i).getWeight()/num2 + " \n");
}
System.out.println();
for(int i = 0;i<list4.size();i++){
System.out.print(list3.get(i)+"的编码为:"+list4.get(i)+" \n");
}
System.out.println();
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);
}
}
}
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 = "";
}
}
}
System.out.println();
System.out.println("编码前:" + temp);
System.out.println("编码后为:\n"+ result);
System.out.println("解码后:"+"\n"+temp3);
File file2 =new File("C:\\Users\\机械革命.000\\Desktop\\程序设计\\大二上\\哈夫曼\\output.txt");
Writer out =new FileWriter(file2);
out.write(result); // 将哈夫曼编码放入output.txt中
out.close();
}
}
实现时的问题
- 问题一:在看到
Collections.sort
方法时不了解该方法,也不了解如何使用。 - 问题一解决:在JAVA API中查询,查到了该方法,如图:
该方法是一个排序方法,升序排序。
- 问题二:
Queue.offer
方法不了解。 - 问题二解决:查询JAVA API,如图:
用于插入元素。