哈夫曼树:
1、给定n个权值作为n个叶子节点,构造一棵二叉树,若该树的带权路径长度WPL达到最小,就称为最优二叉树,也叫哈夫曼树;
2、哈夫曼树是WPL最小的树,权值较大的节点离根节点比较近;
概念说明:
①路径:在一棵树中,从一个节点往下可以达到的孩子或者孙子节点之间的通路,称为路径;
②路径长度:通路中分支的数目称为路径长度
若规定根节点的层数为1,则从根节点到第N层节点的路径长度为 N-1
③节点的权:将树中节点赋给一个某种含义的数值,组这个数值称为该节点的权;
④节点的带权路径长度:从根节点到该节点之间的路径长度与该节点的权的乘积;
例如:
从根节点到叶子节点13,其路径长度为2,该节点的权值就是13,该节点的带权路径长度为13 * 2 = 26;
树的带权路径长度 : 所有叶子节点的带权路径长度之和;
WPL最小的二叉树(哈夫曼树)才叫最优二叉树
例如上述三个图,只有图二的WPL = 59 是最小的,所以图二才是哈夫曼树,其构成的才是最优二叉树;
哈夫曼树创建思路:
给定一个数列{13,7,8,3,29,6,1},要求转化成哈夫曼树
思路分析:
①从小到达进行排序,每个数据都是一个节点,每个节点可以看成是一棵最简单的二叉树
②取出其中两个权值最小的节点,构成一棵新的二叉树;该新的二叉树的根节点权值就是前面两棵子二叉树根节点的权值之和;
③再将这棵新的二叉树,以根节点的权值大小再次排序,不断重复1-2-3-4步骤,直到数列中,所有的元素都被处理,就得到一棵哈夫曼树;
第一次排序后,从序列中选出两个最小的,1、3,将其构成新的二叉树,根节点权值 = 1 + 3 = 4
此时再对数列进行排序,将 4 和 6 取出构成新的二叉树,根节点权值为 6 + 4 = 10;
再对数列进行排序,此时序列中最小的两个权值分别为 7 和 8,将其构成一棵新的二叉树,根节点权值为 7 + 8 = 15;
再对数列进行排序,取出两个最小的,分别为 10 和 13,组成新的二叉树,根节点的权值为 10 + 13 = 25;
再对数列进行排序,取出两个最小的,分别为 15 和 23,组成新的二叉树,根节点的权值为 15 + 23 = 38;
最后再将数列中最后两个数取出,分别为 29 和 38 ,组成新的二叉树,其根节点的权值为 29 + 38 = 67;
此时数列中的数据已经全部取出,此时构建成的数就是哈夫曼树;
哈夫曼树代码实现:
首先创建节点类,表示树的每个节点,因为每次都要对序列进行排序,所以需要声明一个接口Comparable,使用该接口的方法;
class Node implements Comparable<Node> {
int value; //节点权值
Node left; //指向左子节点
Node right; //指向右子节点
//构造器
public Node(int value) {
this.value = value;
}
//前序遍历
public void preOrder() {
System.out.println(this);
if (this.left != null) {
this.left.preOrder();
}
if (this.right != null) {
this.right.preOrder();
}
}
@Override
public String toString() {
return "Node [value = " + value + "]";
}
@Override
public int compareTo(Node o) {
//表示从小到大进行排序
return this.value - o.value;
}
}
创建哈夫曼树代码
因为每次都要遍历arr数组,所以为了方便,就将arr中的每个元素构成一个Node节点,将每个 Node 放入到 ArrayList 中,这样排序和添加 Node 的时候就可以直接调用 ArrayList 中的 add、remove方法,排序的时候就可以直接调用 Comparable 接口中的 sort 方法;
/**
* @param arr 需要创建成哈夫曼树的数组
* @return 创建好后的哈夫曼树的root节点
*/
public static Node createHuffmanTree(int[] arr) {
//1、遍历arr数组
//2、将arr的每个元素构成一个Node
//3、将Node放入到ArrayList中
List<Node> nodes = new ArrayList<Node>();
for (int value : arr) {
nodes.add(new Node(value)); //将数组中的每个元素都以Node的形式放入到ArrayList中
}
//处理的过程是一个循环的过程
while (nodes.size() > 1) {
//从小到达进行排序
Collections.sort(nodes);
System.out.println("nodes = " + nodes);
//取出根节点权值最小的两棵二叉树
//1、取出权值最小的节点(二叉树)
Node leftNode = nodes.get(0);
//2、取出第二小的节点(二叉树)
Node rightNode = nodes.get(1);
//构建新的二叉树
Node parent = new Node(leftNode.value + rightNode.value);
parent.left = leftNode;
parent.right = rightNode;
//从ArrayList中删除处理过的二叉树
nodes.remove(leftNode);
nodes.remove(rightNode);
//将parent加入到nodes中
nodes.add(parent);
}
//返回哈夫曼树的root节点
return nodes.get(0);
}
为了测试哈夫曼树是否构造成功,可以使用前序遍历的方式,遍历一下构造好的二叉树是否符合哈夫曼树的特点;
//前序遍历
public static void preOrder(Node root) {
if (root != null) {
root.preOrder();
}
else {
System.out.println("空树,无法遍历");
}
}
测试类:
前序遍历运行结果:
前序遍历的结果与哈夫曼树的前序遍历结果一直,所以哈夫曼树构造成功;