前几篇文章我们学习二叉树、顺序二叉树、线索化二叉树、堆,接下来我们继续学习有关于树的下一个应用结构:哈夫曼树
一、什么是哈夫曼树
基本介绍
1.给定n个权值
作为n个叶子结点
,构造一棵二叉树
,若该树的带权路径长度1(wpl)达到最小
,称这样的二叉树为最优叉树
。也称为哈夫曼树(HuffmanTree)
,还有的书翻译为霍夫曼树
。
wpl:weight pathI ength
2.哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近
。
问题分析
那么这时就有新的问题出现!需要我们解决了
1.什么是路径呢?路径长度是什么?
2.什么是权和带权路径长度是什么?
3.带权路径长度最短的树,那么树的带权路径长度是什么?
什么是路径和路径长度?
在一棵树中,从一个结点
往下可以达到的孩子或孙子结点
之间的通路,称为路径
。
通路中分支的数目
称为路径长度
。若规定根结点
的层数为 1
,则从根结点到第L层结点
的路径长度为L-1
。
什么是结点的权和带权路径长度?
若将树中结点赋给一个有着某种含义的数值
,则这个数值
称为该结点的权
。
结点的带权路径长度为:从根结点到该结点之间的路径长度
与该结点的权的乘积
。
什么是树的带权路径长度?
树的带权路径长度规定为:所有叶子结点
的带权路径长度之和
,记为WPL(weighted path length)
,权值越大的结点离根结点越近的二叉树才是最优二叉树
。
通过不同的WPL认识哈弗曼树
若该树的带权路径长度1(wpl)达到最小
,称这样的二叉树为最优叉树
。也称为哈夫曼树(HuffmanTree)
哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近
如上图所示我们发现中间的树
其实就是哈夫曼树(HuffmanTree)
,并且满足权值较大的结点离根较近
权值越大的叶子节点越靠近根节点,权值越小的叶子节点越远离根节点。
二、通过示例认识哈夫曼树
将一个权重数列{13, 7, 8,3,29,6,1}
,求转成一颗赫夫曼树
.
思路图解分析
1.将数列{13, 7, 8, 3, 29, 6, 1}
,进行从小到大排序
2.新数列的每个数看成最简单根节点的二叉树
3.取出权值最小的两颗二叉树组成新的二叉树,为两最小的树之和
4.将新二叉树以跟节点的权重值再次从小到大排序,不断重复步骤
我们发现每次都需要根据节点权重值进行大小排序
,那么我们能不能使得对象节点本身就具有比较
的功能呢?
答案是有的:我们的自定义对象Node实现Comparable接口
同时当我们发现排序后,数列的前两个就是最小的权值。
那么根据图所示我们来上节点的代码把
//创建节点类
//为了让Node 对象持续排序Conllections集合排序
//让Node 实现Comparable接口
class Node implements Comparable<Node>{
int value;//结点权值
Node left;//指向左节点
Node right;//指向右节点
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node[" +"value=" + value +']';
}
@Override
public int compareTo(Node o) {
//表示从小到大进行排序
//从大到小进行排序 - (this.value - o.value);
return this.value - o.value;
}
}
核心代码分析
public static void createHuffmanTree(int[] arr ){
/**
* 操作思路
* 1.遍历arr数组
* 2.将arr数组里的元素构成Node节点
* 3.将Node 放入ArrayList中
* 4.进行从小到大排序
*/
List<Node> nodes = new ArrayList<Node>();
for (int itme:arr) {
nodes.add(new Node(itme));
}
//进行排序,ArrayList里的sort排序Node 需要实现Comparable接口
Collections.sort(nodes);
System.out.println(nodes);
}
我们先来测试看看第一步:将数列构成节点并进行从小到大排序
public static void main(String[] args) {
int arr[] = {13,7,8,3,29,6,1};
createHuffmanTree(arr);
}
运行结果如下:
[Node[value=1], Node[value=3], Node[value=6], Node[value=7], Node[value=8], Node[value=13], Node[value=29]]
如上所示,第一步看来还是成功了的,那么接下来进行第二步
进行第二步:取出权值最小的两颗二叉树组成新的二叉树,为两最小的树之和
public static void createHuffmanTree(int[] arr ){
//省略第一步:将数列构成节点并进行从小到大排序代码
/**
* 操作思路
* 1.取出两个权值最小的节点二叉树
* 2.根据两个权值最小的二叉树构建成一个新的二叉树
* 3.删除原先两个权值最小的节点二叉树
* 4.将新的二叉树放入队列,并构建新的队列
* 5.新的队列进行从小到大排序 */
//取出第一个权值最小的二叉树
Node leftNode = nodes.get(0);
//取出第二个权值最小的二叉树
Node rightNode = nodes.get(1);
//根据两个权值最小的二叉树构建成一个新的二叉树 同时构建连接
Node parentNode = new Node(leftNode.value + rightNode.value);
parentNode.left = leftNode;
parentNode.right = rightNode;
//删除原先两个权值最小的节点二叉树
nodes.remove(leftNode);
nodes.remove(rightNode);
//将新的二叉树放入队列,并构建新的队列
nodes.add(parentNode);
Collections.sort(nodes);
System.out.println("第一次处理后:"+nodes);
}
运行结果如下:
[Node[value=1], Node[value=3], Node[value=6], Node[value=7], Node[value=8], Node[value=13], Node[value=29]]
第一次处理后:[Node[value=4], Node[value=6], Node[value=7], Node[value=8], Node[value=13], Node[value=29]]
细节规律小结
1.新的二叉树添加后、就会删除原两个权值最小的二叉树
2.处理完后需要重新排序从小到大
3.如思路分析图所示重复步骤操作后队列里只剩下一个节点
整合核心代码
public static Node createHuffmanTree(int[] arr ){
/**
* 操作思路
* 1.遍历arr数组
* 2.将arr数组里的元素构成Node节点
* 3.将Node 放入ArrayList中
* 4.进行从小到大排序
*/
List<Node> nodes = new ArrayList<Node>();
for (int itme:arr) {
nodes.add(new Node(itme));
}
while(nodes.size() >1){
//进行排序从小到大
Collections.sort(nodes);
System.out.println(nodes);
/**
* 操作思路
* 1.取出两个权值最小的节点二叉
* 2.根据两个权值最小的二叉树构建成一个新的二叉树
* 3.删除原先两个权值最小的节点二叉树
* 4.将新的二叉树放入队列,并构建新的队列
* 5.新的队列进行从小到大排序
*/
//取出第一个权值最小的二叉树
Node leftNode = nodes.get(0);
//取出第二个权值最小的二叉树
Node rightNode = nodes.get(1);
//根据两个权值最小的二叉树构建成一个新的二叉树 同时构建连接
Node parentNode = new Node(leftNode.value + rightNode.value);
parentNode.left = leftNode;
parentNode.right = rightNode;
//删除原先两个权值最小的节点二叉树
nodes.remove(leftNode);
nodes.remove(rightNode);
//将新的二叉树放入队列,并构建新的队列
nodes.add(parentNode);
}
//因为队列只剩下最后一个节点所以直接返回即可
return nodes.get(0);
}
接下来让我们验证一下是否如图所示成功绘制成哈夫曼树
使用前序遍历打印留在队列的root 节点,还记得前序遍历吗?
前序遍历特点:前序是先输出父节点,再遍历左子树和右子树
class Node implements Comparable<Node>{
//省略Node 逻辑代码
//添加前序遍历代码
public void preOrder(){
System.out.println(this);
if(this.left!=null){
this.left.preOrder();
}
if(this.right!=null){
this.right.preOrder();
}
}
}
//添加根节点遍历方法
public static void preOrder(Node root){
if(root!=null){
root.preOrder();
}else{
System.out.println("该哈弗曼树为空!无法遍历");
}
}
我们的数列{13, 7, 8,3,29,6,1}
,组装成哈弗曼树后的前序遍历,思考一下是什么呢?
public static void main(String[] args) {
int arr[] = {13,7,8,3,29,6,1};
Node root = createHuffmanTree(arr);
preOrder(root);
}
运行结果如下:
[Node[value=1], Node[value=3], Node[value=6], Node[value=7], Node[value=8], Node[value=13], Node[value=29]]
[Node[value=4], Node[value=6], Node[value=7], Node[value=8], Node[value=13], Node[value=29]]
[Node[value=7], Node[value=8], Node[value=10], Node[value=13], Node[value=29]]
[Node[value=10], Node[value=13], Node[value=15], Node[value=29]]
[Node[value=15], Node[value=23], Node[value=29]]
[Node[value=29], Node[value=38]]
Node[value=67]
Node[value=29]
Node[value=38]
Node[value=15]
Node[value=7]
Node[value=8]
Node[value=23]
Node[value=10]
Node[value=4]
Node[value=1]
Node[value=3]
Node[value=6]
Node[value=13]
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。