一、 哈夫曼树的基本概念
路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径
结点的路径长度:两结点之间路径上的分支数
树的路径长度:从树根到每一个结点的路径长度之和.
权(weight):将树中结点赋给一个有着某种含义的数值,则这个数值秒针为该结点的权
结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积.
树的带权路径长度:树中所有叶子结点的带权路径长度之和.记作:WPL(Weighted Path Length)
哈夫曼树:最优树(带权路径长度(WPL)最短的树)
注:"带权路径长度最短"是在"度相同"的树中比较而得的结果,因此有最优二叉树,最优三叉树之称等等.
哈夫曼树:最优二叉树(带权路径长度最短的二叉树)
特点:
1满二叉树不一定是哈夫曼树.
2哈夫曼树中权越大的叶子离根越近.
3具有相同带权结点的哈夫曼树不惟一.
二、哈夫曼树的构造算法
因为哈夫曼树权越大的叶子离根越近的特点,所以,构造哈夫曼树时首先选择权值小的叶子结点.
哈夫曼算法(构造哈夫曼树的方法)
(1)根据n个给定的权值构成n棵二叉树的森林,森林中每一棵树只有一个带权的根结点(构造森林全是根)
(2)在森林中,选取两棵根结点的权值最小的树作为左右子树,构造一棵新的二叉树,且设置新的二叉树的根结点的权值为其左右子树上根结点的权值之和.(选用两小造新树)
(3)在森林中删除这两棵树,同时将新得到的二叉树加入到森林中.(删除两小添新人)
(4)重复(2)和(3),直到森林中只有一棵树为止,这棵树即为哈夫曼树.(重复2,3剩单根)
例子:有4个结点a,b,c,d,权值分别人8,6,3,5,构造哈夫曼树。
当最后森林只剩下一个根时,这棵树就是哈夫曼树
哈夫曼树当中:
1,只有度为0或2的结点,没有度为1的结点
2,包含n个叶子结点的哈夫曼树中共有2n-1个结点
三、哈夫曼树JAVA代码实现
public class HuffmanTree {
public static void main(String[] args) {
int arr[] ={3,6,15,20};
Node huffmanTree = createHuffmanTree(arr);
if (huffmanTree!=null){
huffmanTree.preOrder();
}
}
public static Node createHuffmanTree(int[] arr){
// 创建一个集合,存入创建的节点
List<Node> nodeList = new ArrayList<>();
for (int item : arr) {
nodeList.add(new Node(item));
}
// 因为每次都会remove一些节点,最终会在list中剩下一个节点,这个节点就是根节点
while (nodeList.size() > 1){
// 从小到达排序list
Collections.sort(nodeList);
// 取出前两个最小的,第一个作为左节点,第二个作为右节点
Node leftNode = nodeList.get(0);
Node rightNode = nodeList.get(1);
// 将权重+路径和赋值给父节点,将父节点的左右节点挂上
Node parentNode = new Node(leftNode.getValue()+rightNode.getValue());
parentNode.setLeft(leftNode);
parentNode.setRight(rightNode);
// 移除最小的两个节点,将父节点放入list集合中,进行下一轮
nodeList.remove(leftNode);
nodeList.remove(rightNode);
nodeList.add(parentNode);
}
// 返回最终的根节点
return nodeList.get(0);
}
}
class Node implements Comparable<Node>{
private int value;
private Node left;
private Node right;
public Node(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
// 从小到大排序
@Override
public int compareTo(Node node) {
return this.value - node.value;
}
// 前序遍历
public void preOrder(){
System.out.println(this.toString());
if (this.left!=null){
this.left.preOrder();
}
if (this.right!=null){
this.right.preOrder();
}
}
}
四、哈夫曼树的应用(哈夫曼编码)
构造过程:
1. 构造哈夫曼树
2. 结点的左分支标0,右分支标1
例子:还是上面那个例子,在哈夫曼树的基础上,结果为:
8:0
6:10
3:110
5:111
哈夫曼编码的优点:
- 能够压缩20%~90%的数据
- 无损压缩
- 此编码满足前缀编码,不会造成匹配多义性
需要注意的是:
- 在数据排序时不稳定而结果会造成最终的编码结果不同,但是编码后的长度是一样的。
- 数据重复不多,压缩效果不明显。