文章收藏的好句子:你的努力,绝不会辜负你。
目录
1、赫夫曼树
1、1 赫夫曼树的基本介绍
1、2 赫夫曼树的重要概念
1、3 赫夫曼树创建思路
1、4 赫夫曼树的代码实现
1、赫夫曼树
1、1 赫夫曼树的基本介绍
给定 n 个权值作为 n 个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树;赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
“ 权值 ” 、“ 树的带权路径长度 ” 和 “ 赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近 ” 这3句话好像理解不了?没关系,我们先保留悬念,下面会分析到。
1、2 赫夫曼树的重要概念
为了更好的说明这些概念,我们先画一棵普通的二叉树,如图1所示:
路径:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路;例如图1中的2节点到8节点的路径为 2 -> 4 -> 8 。
路径长度:通路中分支的数目,若规定根结点的层数为1,则从根结点到第 L 层结点的路径长度为 L-1;例如8节点所在的一层是第4层,那么8节点的路径长度为 4 - 1 。
节点的权值:将树中结点赋给一个有着某种含义的数值,这个数值称为该结点的权;例如9节点的的数值是9,那么节点的权值就是9 。
结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积;例如9节点的带权路径长度就等于3(4 - 1)乘以9 。
树的带权路径长度:所有叶子结点的带权路径长度之和,记为WPL (weighted path length);例如图1中树的带权路径长度就为 8节点、9节点、5节点、6节点和7节点的带权路径长度之和。
赫夫曼树:就是 WPL 最小的一棵二叉树,也就是树的带权路径长度最小。
1、3 赫夫曼树创建思路
假设我们有一个数列 arr = {13 , 7 , 8 , 3 , 29 , 6 , 2 },将这个数列转换成一棵赫夫曼树,我们该怎么做呢?
好,我们来分析赫夫曼树的创建步骤:
(1)将这个数列从小到大进行排序,将每一个数字,每个数字都是一个节点,每个节点又看成是一颗最简单的二叉树。
(2)取出根节点权值最小的两颗二叉树,组成一颗新的二叉树,该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和。
(3)如果组合完这颗新的二叉树后,没有其他树了,那么就得到一棵赫夫曼树,不必执行(4)步骤。
(4)如果组合完这颗新的二叉树后,还有其他树了,那么就将这颗新的二叉树的根节点的权和其他根节点的权进行组合一个数列,再回到(1)。
有了赫夫曼树的创建步骤,我们就可以对 arr = {13 , 7 , 8 , 3 , 29 , 6 , 2 } 进行转换成赫夫曼树。
(1)看图2,我们把 arr = {13 , 7 , 8 , 3 , 29 , 6 , 2 } 这个数列进行了从小到大排序并把数字变成根节点,然后从最小的2个根节点(2节点和3节点)组合成一棵二叉树,并得到如图3所示的多棵二叉树。
注意:白色的数字的节点是从 arr 这个数列中得到的,而红色的数字的节点是组合而成的新节点,以下出现的图,也是一样的说明。
(2)看图3,用根节点2和根节点3组合成一棵新的二叉树,并把5节点作为它们的根节点(该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和),然后又得到了一个新的数列并从小到大排序 arr = {5 , 6 , 7 , 8 , 13 , 29},又从 arr 数列中选最小的2个根节点组合成一棵二叉树,并得到如图4所示的多棵二叉树。
(3)看图4,用根节点5和根节点6组合成一棵新的二叉树,并把11节点作为它们的根节点(该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和),然后又得到了一个新的数列并从小到大排序 arr = {7 , 8 , 11 , 13, 29},又从 arr 数列中选最小的2个根节点组合成一棵二叉树,并得到如图5所示的多棵二叉树。
(4)看图5,用根节点7和根节点8组合成一棵新的二叉树,并把15节点作为它们的根节点(该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和),然后又得到了一个新的数列并从小到大排序 arr = {11 , 13 , 15 , 29},又从 arr 数列中选最小的2个根节点组合成一棵二叉树,并得到如图6所示的多棵二叉树。
(5)看图6,用根节点11和根节点13组合成一棵新的二叉树,并把24节点作为它们的根节点(该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和),然后又得到了一个新的数列并从小到大排序 arr = {15 , 24 , 29},又从 arr 数列中选最小的2个根节点组合成一棵二叉树,并得到如图7所示的多棵二叉树。
(6)看图7,用根节点15和根节点24组合成一棵新的二叉树,并把39节点作为它们的根节点(该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和),然后又得到了一个新的数列并从小到大排序 arr = {29 , 39},又从 arr 数列中选最小的2个根节点组合成一棵二叉树,并得到如图8所示的一棵二叉树。
到了这里,一开始用数列 arr = {13 , 7 , 8 , 3 , 29 , 6 , 2 } 创建的赫夫曼树已经完成;“ 权值较大的结点离根较近 ” 怎么理解这句话呢?就拿图8的赫夫曼树举例,39节点是不是比15节点权值更大,那39节点相对15节点来说是不是离根节点68比较近,这回理解了吧。
1、4 赫夫曼树的代码实现
图8中的二叉树,中序遍历的结果是:29 68 7 15 8 39 2 5 3 11 6 24 13,有关中序遍历的文章可以看Java版的数据结构和算法(四)这篇,好了,现在我们用代码实现一把,用数列 arr = {13 , 7 , 8 , 3 , 29 , 6 , 2 } 创建一棵赫夫曼树。
(1)创建一个节点类 Node :
/**
* Node 实现 Comparable 是为了让Node 对象持续排序 Collections 集合排序
*/
public class Node implements Comparable<Node>{
private int value;
private Node left;
private Node right;
public Node(int value) {
super();
this.value = value;
}
public int getValue() {
return 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 int compareTo(Node arg0) {
return this.value - arg0.value;
}
@Override
public String toString() {
return "Node [value=" + value + "]";
}
}
(2)创建一个构造赫夫曼树的类 Test :
public class Test {
Node root;
public static void main(String[] args) {
int[] arr = { 13, 7, 8, 3, 29, 6, 2 };
Test test = new Test();
test.root = test.createHuffmanTree(arr);
test.midOrder();
}
/**
* 构建一棵哈夫曼树
*
* @param arr
*/
private Node createHuffmanTree(int[] arr) {
List<Node> list = new ArrayList<Node>();
for (int i = 0; i < arr.length; i++) {
// 用数组的元素作为节点的权值,并把节点保存在 list 集合中
list.add(new Node(arr[i]));
}
while (list.size() > 1) {
// 排序,从小到大排序
Collections.sort(list);
// System.out.println("list = " + list);
/**
* 这个一定是权值最小的节点
*/
Node minNode = list.get(0);
/**
* 除了 minNode 节点,该节点就是权值最小的节点,只是该节点比 minNode 节点大, 比其他节点都小
*/
Node secondMinNode = list.get(1);
/**
* 构建一棵新的二叉树
*/
Node parentNode = new Node(minNode.getValue()
+ secondMinNode.getValue());
parentNode.setLeft(minNode);
parentNode.setRight(secondMinNode);
/**
* 从 list 集合中删除2棵已经构建成一棵二叉树的2个节点
*/
list.remove(minNode);
list.remove(secondMinNode);
/**
* 将新的二叉树的父节点加入到 list 集合中
*/
list.add(parentNode);
}
/**
* 构建完哈夫曼树后,list 集合中的第一个节点肯定是根节点,除非该方法传入的数组arr是空的
*/
return list.get(0);
}
// 中序遍历
private void midOrder() {
if (root != null) {
System.out.println("二叉树的中序遍历~~");
midOrder(root);
} else {
System.out.println("这是一棵空树");
}
}
public void midOrder(Node node) {
// 递归向左子树进行前序遍历
if (node.getLeft() != null) {
midOrder(node.getLeft());
}
// 输出当前节点
System.out.println(node);
// 递归向右子树进行前序遍历
if (node.getRight() != null) {
midOrder(node.getRight());
}
}
}
程序运行如下所示:
看见没有,构造完成赫夫曼树后,我们再对赫夫曼树中序遍历,得到的结果和图8中的二叉树的中序遍历结果是一样的。