20172311-哈夫曼编码测试
哈夫曼编码与哈夫曼树
哈夫曼树
哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的带权路径长度记为WPL= (W1L1+W2L2+W3L3+...+WnLn),N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。可以证明哈夫曼树的WPL是最小的。哈夫曼编码
哈夫曼编码(HuffmanCoding)是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。uffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长 度最短的码字,有时称之为最佳编码,一般就叫作Huffman编码。哈夫曼编码是一种无前缀编码。解码时不会混淆。其主要应用在数据压缩,加密解密等场合。
哈夫曼编码步骤
1、对给定的n个权值{W1,W2,W3,...,Wi,...,Wn}构成n棵二叉树的初始集合F={T1,T2,T3,...,Ti,...,Tn},其中每棵二叉树Ti中只有一个权值为Wi的根结点,它的左右子树均为空。(为方便在计算机上实现算法,一般还要求以Ti的权值Wi的升序排列。)
2、在F中选取两棵根结点权值最小的树作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和。
3、从F中删除这两棵树,并把这棵新的二叉树同样以升序排列加入到集合F中。
4、重复二和三两步,直到集合F中只有一棵二叉树为止。
举例如下:
假如我有A,B,C,D,E五个字符,出现的频率(即权值)分别为5,4,3,2,1,那么我们第一步先取两个最小权值作为左右子树构造一个新树,即取1,2构成新树,其结点为1+2=3,如图:
虚线为新生成的结点,第二步再把新生成的权值为3的结点放到剩下的集合中,所以集合变成{5,4,3,3},再根据第二步,取最小的两个权值构成新树,如图:
再依次建立哈夫曼树,如下图:
其中各个权值替换对应的字符即为下图:
所以各字符对应的编码为:A->11,B->10,C->00,D->011,E->010
用Java实现哈夫曼编码
创建哈夫曼树的结点类
该结点类中包括哈夫曼树的父节点,左孩子和右孩子;权值以及char类型的元素(存放26个英文字母用)。代码如下:
//哈夫曼树的节点类
public class huffmanNode implements Comparable<huffmanNode>
{
private int weight;
private huffmanNode parent,left,right;
private char element;
public huffmanNode(int weight, char element , huffmanNode parent, huffmanNode left, huffmanNode right) {
this.weight = weight;//权值
this.element = element;//char元素
this.parent = parent;//父节点
this.left = left;//左孩子
this.right = right;//右孩子
}
public int getWeight() {
return weight;
}
@Override
public int compareTo(huffmanNode huffmanNode) {
return this.weight - huffmanNode.getWeight();
}
public huffmanNode getParent() {
return parent;
}
public void setParent(huffmanNode parent) {
this.parent = parent;
}
public huffmanNode getLeft() {
return left;
}
public void setLeft(huffmanNode left) {
this.left = left;
}
public huffmanNode getRight() {
return right;
}
public void setRight(huffmanNode right) {
this.right = right;
}
public char getElement() {
return element;
}
public void setElement(char element) {
this.element = element;
}
}
创建哈夫曼树类
1.编写构造函数,接收一个哈夫曼节点类的数组,使用最小堆生成相应的哈夫曼树。构造函数如下:
public huffmanTree(huffmanNode[] huffmanArray) {
huffmanNode parent = null;
SmallArrayHeap<huffmanNode> heap = new SmallArrayHeap<>();
for (int i=0;i<huffmanArray.length;i++)
{
heap.addElement(huffmanArray[i]);
}
for(int i=0; i<huffmanArray.length-1; i++) {
huffmanNode left = heap.removeMin(); //左孩子是最小节点
huffmanNode right = heap.removeMin();
parent = new huffmanNode(left.getWeight()+right.getWeight(),' ',null,left,right);
left.setParent(parent);
right.setParent(parent);
heap.addElement(parent);
}
root = parent;
}
2.编写一个哈夫曼树的中序遍历方法用于得到基本元素的编码的数组,中序遍历方法如下:
protected void inOrder( huffmanNode node,
ArrayList<huffmanNode> tempList)
{
if (node != null)
{
inOrder(node.getLeft(), tempList);
if (node.getElement()!=' ')
tempList.add(node);
inOrder(node.getRight(), tempList);
}
}
3.利用无序列表和栈以及中序遍历方法实现得到需要的基本元素的编码的数组的方法,方法如下:
public String[] getEncoding() {
ArrayList<huffmanNode> arrayList = new ArrayList();
inOrder(root,arrayList);
for (int i=0;i<arrayList.size();i++)
{
huffmanNode node = arrayList.get(i);
String result ="";
int x = node.getElement()-'a';
Stack stack = new Stack();
while (node!= root)
{
if (node==node.getParent().getLeft())
stack.push(0);
if (node==node.getParent().getRight())
stack.push(1);
node=node.getParent();
}
while (!stack.isEmpty())
{
result +=stack.pop();
}
codes[x] = result;
}
return codes;
}
使用哈夫曼树编码解码测试
1.读取文件,并打印文件中各字母出现的频率,代码如下:
File file = new File("C:\\Users\\user\\IdeaProjects\\socialsea\\socialsea\\src\\HuffmanTest\\readFile");
Scanner scan = new Scanner(file);
String s = scan.nextLine();
System.out.println(s);
int[] array = new int[26];
for (int i = 0; i < array.length; i++) {
array[i] = 0;
}
for (int i = 0; i < s.length(); i++) {
char x = s.charAt(i);
array[x - 'a']++;
}
System.out.println("各字母出现频率:");
for (int i = 0; i < array.length; i++) {
System.out.print((char)('a'+i)+":"+(double) array[i] / s.length()+" ");
}
2.构造有26个字母元素的哈夫曼树,代码如下:
huffmanNode[] huffmanTreeNodes = new huffmanNode[array.length];
for (int i = 0; i < array.length; i++) {
huffmanTreeNodes[i] = new huffmanNode(array[i], (char) ('a' + i), null, null, null);
}
huffmanTree huffmanTree = new huffmanTree(huffmanTreeNodes);
3.使用哈夫曼树类中getEncoding()
方法得到a——z各字母对应的编码并打印出来。代码如下:
System.out.println("各字母的编码");
String[] codes = huffmanTree.getEncoding();
for (int i = 0; i < codes.length; i++) {
System.out.println((char) ('a' + i) + ":" + codes[i]);
}
4.对readFile文件中内容进行编码并输出,然后写入到codeResult文件中去,代码如下:
//进行编码:
String result = "";
for (int i = 0; i < s.length(); i++) {
int x = s.charAt(i) - 'a';
result += codes[x];
}
System.out.println("编码结果:"+result);
//写入文件
File file1 = new File("C:\\Users\\user\\IdeaProjects\\socialsea\\socialsea\\src\\HuffmanTest\\codeResult");
FileWriter fileWriter = new FileWriter(file1);
fileWriter.write(result);
fileWriter.close();
5.从codeResult文件中读取编码后的内容并解码,然后写入到decodeResult文件中,代码如下:
//从文件读取
Scanner scan1 = new Scanner(file1);
String s1 = scan1.nextLine();
huffmanNode huffmanTreeNode = huffmanTree.getRoot();
//进行解码
String result2 = "";
for (int i = 0; i < s1.length(); i++) {
if (s1.charAt(i) == '0') {
if (huffmanTreeNode.getLeft() != null) {
huffmanTreeNode = huffmanTreeNode.getLeft();
}
} else {
if (s1.charAt(i) == '1') {
if (huffmanTreeNode.getRight() != null) {
huffmanTreeNode = huffmanTreeNode.getRight();
}
}
}
if (huffmanTreeNode.getLeft() == null && huffmanTreeNode.getRight() == null) {
result2 += huffmanTreeNode.getElement();
huffmanTreeNode = huffmanTree.getRoot();
}
}
System.out.println("解码结果:"+result2);
//写入文件
File file2 = new File("C:\\Users\\user\\IdeaProjects\\socialsea\\socialsea\\src\\HuffmanTest\\decodeResult");
FileWriter fileWriter1 = new FileWriter(file2);
fileWriter1.write(result2);
fileWriter1.close();
实验结果截图
readFile文件
codeResult文件
decodeResult文件
运行结果
码云链接
感悟
刚开始的时候感觉通过哈夫曼树实现编码与解码肯定是非常复杂,首先每个字母对应的编码去怎么设计就好像无法完成,但是在学习的过程中逐渐发现其实也没有想象中那么难,把大的问题拆分开来,一步一步的去解决,最终就能将问题解决了,本次实验让我受益匪浅!