目录
实验七 哈夫曼编码
实验目的
设计并实现二叉搜索树。
在二叉搜索树中查找元素。
在二叉搜索树中插入元素
遍历二叉树中的元素。
从二叉搜索树中删除元素。
使用二叉搜索树实现用于压缩数据的霍夫曼编码。
实验内容
使用实验六中的Heap类,实现HuffmanCode类中的方法,实现对一个字符串的编码和解码。具体要求如下:
输入输出要求:
- 输入为一个字符串,字符在ASCII字符集范围内。
- 控制台输出为①字母及对应字母的出现次数和哈夫曼编码②编码后的加密字符串③通过加密字符串解码后得到的字符串(要与输入字符串相同才为正确)。示例:
代码实现要求:
- 实现wordToCode(String text)方法,输入为程序需要为之编码的字符串,返回HashMap<Character,String>类型的对象,保存字符串中的字符及其对应的哈夫曼编码。
- 实现code(HashMap<Character,String> wordToCode)方法,输入为上个方法中输出的字符及其哈夫曼编码的HashMap对象,返回编码后得到的字符串。
测试方法:
在本实验的测试类HuffmanCodeTest中,实现对本实验的测试首先输入测试字符串,再调用wordToCode(String text)、code(HashMap<Character,String> wordToCode)生成哈夫曼编码,使用得到的编码后字符串进行解码,如果得到的字符串与输入一致,即可通过测试。测试类的代码见代码框架。
实验代码框架
HuffmanCode类
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
public class HuffmanCode {
String input="";
public static void main(String[] args) {
}
public String code(HashMap<Character,String> wordToCode){
}
public HashMap<Character,String> wordToCode(String text){
}
public class Tree implements Comparable<Tree>{
}
public class Node{
}
}
HuffmanCodeTest类
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.HashMap;
import java.util.Set;
import org.junit.Test;
import static org.junit.Assert.*;
public class HuffmanCodeTest {
@Before
public void setUp() throws Exception {
System.out.println("begin");
}
@After
public void tearDown() throws Exception {
System.out.println("End and success");
}
@Test
public void huffmanCode(){
HuffmanCode x=new HuffmanCode();
String inputString="abbcccddddeeeeeffffffggggggg";
HashMap<Character,String> map=x.wordToCode(inputString);
System.out.println(x.code(map));
String code=x.code(map);
String decode="";
HashMap<String,Character> map2=new HashMap<>();
Set<Character> set=map.keySet();
int maxCodeLength=0;
for(Character key:set){
map2.put(map.get(key),key);
if(map.get(key).length()>maxCodeLength)
maxCodeLength=map.get(key).length();
}
int i=0;
int len=Math.min(maxCodeLength,code.length());
while (i<code.length()){
String s=code.substring(i,i+len);
Character character=map2.get(s);
if(character==null)
len--;
else {
decode+=character;
i+=len;
len=Math.min(maxCodeLength,code.length()-i);
}
}
System.out.println(decode);
assertEquals(inputString,decode);
}
}
实验设计的思路与考量
哈夫曼编码的概念
哈夫曼编码通过使用更少的比特对更长出现的字符编码,从而压缩数据。字符的编码是基于字符在文本中出现的次数使用二叉树来构建的,该树称为霍夫曼编码。节点的左边和右边分别被赋值0和1。每个字符都是树中的一个叶节点,字符的编码从根节点到叶子结点的路径上变得值所组成。
哈夫曼树的构建
为了构建一棵哈夫曼树,使用如下算法:
- 从由树构成的森林开始。每棵树都包含一个表示字符的节点。每个结点的权重为该字符在文本中出现的次数。
- 重复以下步骤来合并树,直到只有一棵树为止:选择两棵有最小权重的树,创建一个新的节点作为它们的父节点。这棵新树的权重是子树的权重和。
- 对于每个内部节点,给它的做编制赋值为0,右边赋值为1。所有的叶子节点都表示文本中的个字符。
其中没有编码是另一个编码的前缀,这个属性保证了流可以无二义性地解码。
这里使用的算法是贪婪算法的一个实例。贪婪算法经常用于解决优化问题。算法做出局部最优的选择,并希望这样的选择会导致全局最优。
程序的实现
- 需要自行实现一个内部类表示节点,有左子树、右子树、节点权重值和它代表的字符。
- 需要自行实现一个内部类表示树,有根节点,并且可以代表初始生成的树(以权重和字符生成叶节点),也可以表示结合生成新树的节点(以左子树和右子树生成新树,权重为两棵树的权重之和)。
- 生成哈夫曼树的时候需要用到实验六实现的Heap类。当时实现的是大根堆,而我们需要每次选出权重值最小和次小的树进行合并,所以需要重载compare方法,实现逆转,可以在实现内部类Tree的时候进行重载(因为对比的是树的根节点的值,而内部类Tree中就有根节点)。示例如下:
public class Tree implements Comparable<Tree>{
Node root;
public Tree(Tree t1,Tree t2){
root=new Node();
root.left= t1.root;
root.right=t2.root;
root.weight=t1.root.weight+t2.root.weight;
}
public Tree(int weight,char element){
root=new Node(weight,element);
}
@Override
public int compareTo(Tree t) {
if(root.weight<t.root.weight)
return 1;
else if(root.weight==t.root.weight)
return 0;
else
return -1;
}
}