QQ不能直接传送文件夹,我们一般都会将它压缩成一个文件之后再发过去,而这里面用到的压缩技术,怎样实现的呢?
今天就来介绍一种最基本的压缩编码方式——赫夫曼编码
实现赫夫曼压缩分为五个部分,大家根据自己需求可以跳跃查看
一 : 赫夫曼树概述.
二:图解创建赫夫曼树流程.
三:代码实现赫夫曼树及注意问题.
四 :创建编码表并进行解码
五:使用赫夫曼编码压缩文件和解压文件
一 :赫夫曼树概述
在说赫夫曼之前先说明三个概念:
1.1路径长度: 从树中的一个结点到另一个结点叫做一条路径,例如下图A的路径从根节点开始是2。
1.2 叶节点的带权路径:权值(节点的值)乘以路径
例如A点的带权路径为:9*2=18
1.3 树的带权路径(WPL)
就是所有节点带权路径之和。大家可以计算一下下图WPL是多少?
a图的WPL为40(9 * 2+4 * 2+5 * 2+2 * 2=40);
b为37;c为50;
我们对比一下 b 和 c,二者节点的权值之和相同,但是b权值大的节点离根节点更近,WPL更小,在这三种树里,b便是最优二叉树。
二 :图解创建赫夫曼树流程
下面我们通过一个例子来理解赫夫曼树创建的全过程:
第一步:对于任意一个数组,我们先通过排序算法将它变为有序数组
第二步:取出最小的两个节点,构成一个新的二叉树。
第三步:很明显他们二者得有关系,我们再创建一个根节点,根节电的权值为两个子节点权值之和
第四步:我们把新生成的二叉树放入原数组排序,原来数组中的元素删除不在排序(根节点权值进入排序)
第五步:我们再次把根节点最小的两个子树(7和新生成的8)取出,构成一个新的二叉树,并进行排序
第六步:在原数组中再次取出根节点最小的两个子树,构成一个新的二叉树
第七到第十步都是在递归
明白了原理,我们开始使用代码先实现一颗赫夫曼树:
三:代码实现赫夫曼树及注意问题
3.1代码实现要点:
3.1.1为方便使用java内部排序方法以及删除,添加元素,我们使用List来存储初始无节点树,注意List泛型为Node节点
3.12 使用Collections.sort(nodes)方法进行排序,得实现Comparable接口,并重写他的compareTo()方法,其中升序排列和降序排列的返回值分别是 :
/**
* 记得要把此接口的泛型改了 Comparable<Node>
* 重写此方法
* 升序: return this.value-o.value;
* 降序:return -(this.value-o.value);
* @return
*/
public int compareTo(Node o) {
return -(this.value-o.value);
}
3.13调用排序方法后打印,是如下的乱码,
这是因为内部转换字符不仅仅有节点的值,还有左右节点的指针,如下图所示
我们需要重写toString方法,只让他显示节点的权值
//重写显示的方法
public String toString() {
return "Node [value=" + value + "]";
}
全部代码如下:
package demo8;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* 赫夫曼树
* 2019年2月17日
*/
public class Huffman {
public static void main(String[] args) {
// 创建数组
int[] arr = new int[] { 3, 8, 5, 7, 14, 11, 29, 23 };
creatHuffman(arr);
}
// 构建哈夫曼树的方法()
public static void creatHuffman(int[] arr) {
// 使用所有元素创建若干个二叉树,使用List
List<Node> nodes = new ArrayList<>();
// 只有一个节点
for (int value : arr) {
nodes.add(new Node(value));
}
// 循环执行,直到只有一个根节点
while (nodes.size() > 1) {
// 排序,调用java的方法进行,需要继承Comparable
Collections.sort(nodes);
// 排序之后直接打印,打印出来的是指针和一堆东西混合,重写他的显示方法to string
System.out.println(nodes);
//取出权值最小的两个
Node left = nodes.get(nodes.size()-1);
Node right = nodes.get(nodes.size()-2);
//创建一个新的树
Node root = new Node(left.value+right.value);
root.setLeftNode(left);
root.setRightNode(right);
//把原来两个二叉树移除
nodes.remove(left);
nodes.remove(right);
//把新树放在原树中排序
nodes.add(root);
}
//上面的方法并不能打印最后一个根节点,出现索引问题,
if(nodes.size()==1){
System.out.println(nodes);
}
}
}
/**
* 2019年2月17日
*/
package demo8;
/**
*
* 赫夫曼树的节点
* 2019年2月17日
*/
public class Node implements Comparable<Node>{
//权值和左右节点
int value;
Node left;
Node right;
public Node(int value){
this.value=value;
}
//设置左子节点
public void setLeftNode(Node left){
this.left= left;
}
//设置右子节点
public void setRightNode(Node right){
this.right=right;
}
/**
* 记得要把此接口的泛型改了
* 重写此方法
* 升序:return this.value-o.value;
* 降序:return -(this.value-o.value);
* @return
*/
public int compareTo(Node o) {
return -(this.value-o.value);
}
//重写显示的方法
public String toString() {
return "Node [value=" + value + "]";
}
}
代码运行结果:一个完整的赫夫曼树就生成了