哈夫曼树是一个特殊的二叉树,它的特殊在于:
哈夫曼树:给定一组具有确定权值的叶子节点,带权路径长度最小的二叉树
举例:给定4个叶子结点,其权值分别为{2,3,4,7},可以构造出形状不同的多个二叉树。
如上图所示,可以根据给定的叶子结点构建出不同的二叉树结构,按照计算以上三个二叉树带权路径长度计算公式得出的WPL分别为32、41、30,根据哈夫曼树定义可知,最右侧WPL=30的这个二叉树就是一个哈夫曼树。
由给定的n个权值 {w1,w2,…,wn} 构造n颗只有一个根结点的二叉树,从而得到一个二叉树集合F= {T1,T2,…,Tn}
在集合F中选取根结点的权值最小的两颗二叉树分别作为左、右子树构造一颗新的二叉树,这颗二叉树的根结点的权值为其左、右子树根结点的权值之和
在集合F中删除作为作为左、右子树的两颗二叉树,并将新建立的二叉树加入到集合F中
重复以上选取与合并、删除与加入两步,当集合F中只剩下一颗二叉树时,这颗二叉树便是哈夫曼树
我们按照上面的例子,初始化集合{2,3,4,7}来模拟下哈夫曼树的推演过程,记录如下:
设置一个数组 huffTree 来存储哈夫曼树中各结点信息,数组元素的数据结构如下图:
数组大小可以通过2n-1来计算获得,为何是2n-1大小,可以参考二叉树性质来计算。
根据上面的初始化、选取与合并、删除与加入、递归的步骤方法推演,进行数组操作过程如下:
参考的资料中是采用朴素的数组进行存储的,数组存储的困难在于不利于动态扩容和调整,这里采用List集合进行存储,实际上底层也是数组存储,只是通过封装好的容器进行调用即可。这里还是延用上面图例中的权值数组 {2,3,4,7},除此之外,还可以使用 {2,3,5,7,5} 这种新构建结点等于已存在权值结点、多个权值结点权值相等各种特殊情况来进行验证。
/**
* 哈夫曼树
* created by guanjian on 2020/12/30 15:43
*/
public class HuffmanTree {
//权值结点
private Integer[] weightArray;
//哈夫曼树结点总数 2*weights - 1
private int huffmanTreeNodeLength;
//哈夫曼树结点数组
private List<HuffmanTreeNode> huffmanTreeNodeList;
//每次获取最小权值数量
private final static int minNodeLength = 2;
public HuffmanTree(Integer[] weightArray) {
Assert.notEmpty(weightArray, "weightArray can not be empty");
this.weightArray = weightArray;
}
/**
* 初始化哈夫曼树结点数组
*/
public void initHuffmanTreeNodeArray() {
//哈夫曼树结点总数 2*weights - 1
this.huffmanTreeNodeLength = 2 * this.weightArray.length - 1;
this.huffmanTreeNodeList = Lists.newArrayListWithCapacity(huffmanTreeNodeLength);
//初始化填充哈夫曼树结点
IntStream.range(0, weightArray.length).forEach(i -> {
//这里将权值灌入到node节点中
huffmanTreeNodeList.add(
HuffmanTreeNode.Builder.aHuffmanTreeNode()
.weight(weightArray[i])
.build()
);
});
System.out.format("初始化完成 huffmanTreeNodeList=%s \n", Arrays.toString(huffmanTreeNodeList.toArray()));
}
/**
* 查找最小权值方法
*/
public List<HuffmanTreeNode> findMinNodes() {
//按权值从小到大升序进行排序
List<HuffmanTreeNode> sortedList = huffmanTreeNodeList.stream()
.filter(x -> !x.isSorted())
.sorted(Comparator.comparing(HuffmanTreeNode::getWeight))
.collect(Collectors.toList());
sortedList.forEach(x -> System.out.format("排序 %s 处理 %s \n", x.getWeight(), x.isSorted()));
//取最小权值的两个node
List<HuffmanTreeNode> list = sortedList.subList(0,
sortedList.size() >= minNodeLength ? minNodeLength : sortedList.size());
if (CollectionUtils.isEmpty(list)) return list;
list.forEach(x -> {
x.setSorted(true);
});
return list