漫画:什么是 “哈夫曼树” ?

简介: 哈夫曼树是由麻省理工学院的哈夫曼博士于1952年发明,这到底是一颗什么样的树呢?刚才我们学习了树的带权路径长度(WPL),而哈夫曼树(Huffman Tree)是在叶子结点和权重确定的情况下,带权路径长度最小的二叉树,也被称为最优二叉树。举个例子,给定权重分别为1,3,4,6,8的叶子结点,我们应当构建怎样的二叉树,才能保证其带权路径长度最小?原则上,我们应该让权重小的叶子结点远离树根,权重大的叶子结点靠近树根。

640.jpg

—————  第二天  —————

 


640.jpg640.jpg640.jpg640.jpg640.jpg640.jpg640.jpg

 

————————————

 

 

640.jpg640.jpg640.jpg640.jpg

 

 

 

概念1:什么是路径?




在一棵树中,从一个结点到另一个结点所经过的所有结点,被我们称为两个结点之间的路径。640.png

 



上面的二叉树当中,从根结点A到叶子结点H的路径,就是ABDH


概念2:什么是路径长度?


 

在一棵树中,从一个结点到另一个结点所经过的“边”的数量,被我们称为两个结点之间的路径长度。

640.png

仍然用刚才的二叉树举例子,从根结点A到叶子结点H,共经过了3条边,因此路径长度是3

概念3:什么是 结点的带权路径长度?


 

树的每一个结点,都可以拥有自己的“权重”(Weight),权重在不同的算法当中可以起到不同的作用。

结点的带权路径长度,是指树的根结点到该结点的路径长度,和该结点权重的乘积。

640.png

假设结点H的权重是3,从根结点到结点H的路径长度也是3,因此结点H的带权路径长度是 3 X 3 = 9


概念4:什么是 树的带权路径长度?


 

在一棵树中,所有叶子结点的带权路径长度之和,被称为树的带权路径长度,也被简称为WPL

640.png

仍然以这颗二叉树为例,树的路径长度是 3X3 + 6X3 + 1X2 + 4X2 + 8X2 = 53

640.jpg640.jpg 

 

哈夫曼树是由麻省理工学院的哈夫曼博士于1952年发明,这到底是一颗什么样的树呢?


哈夫曼树



刚才我们学习了树的带权路径长度(WPL),而哈夫曼树(Huffman Tree)是在叶子结点和权重确定的情况下,带权路径长度最小的二叉树,也被称为最优二叉树。

举个例子,给定权重分别为13468的叶子结点,我们应当构建怎样的二叉树,才能保证其带权路径长度最小?

原则上,我们应该让权重小的叶子结点远离树根,权重大的叶子结点靠近树根。

下图左侧的这棵树就是一颗哈夫曼树,它的WPL46,小于之前例子当中的53

 

640.png

需要注意的是,同样叶子结点所构成的哈夫曼树可能不止一颗,下面这几棵树都是哈夫曼树:

640.png

 

640.jpg640.jpg

 

假设有6个叶子结点,权重依次是23791825,如何构建一颗哈夫曼树,也就是带权路径长度最小的树呢?

640.png



第一步:构建森林


我们把每一个叶子结点,都当做树一颗独立的树(只有根结点的树),这样就形成了一个森林:

640.png

 

在上图当中,右侧是叶子结点的森林,左侧是一个辅助队列,按照权值从小到大存储了所有叶子结点。至于辅助队列的作用,我们后续将会看到。

第二步:选择当前权值最小的两个结点,生成新的父结点


借助辅助队列,我们可以找到权值最小的结点23,并根据这两个结点生成一个新的父结点,父节点的权值是这两个结点权值之和:

640.png

 

第三步:从队列中移除上一步选择的两个最小结点,把新的父节点加入队列

也就是从队列中删除23,插入5,并且仍然保持队列的升序:

 640.png


第四步:选择当前权值最小的两个结点,生成新的父结点

这是对第二步的重复操作。当前队列中权值最小的结点是57,生成新的父结点权值是5+7=12

640.png

 

 

第五步:从队列中移除上一步选择的两个最小结点,把新的父节点加入队列


这是对第三步的重复操作,也就是从队列中删除57,插入12,并且仍然保持队列的升序:

 

640.png

 

 

第六步:选择当前权值最小的两个结点,生成新的父结点


这是对第二步的重复操作。当前队列中权值最小的结点是912,生成新的父结点权值是9+12=21

 640.png

 

第七步:从队列中移除上一步选择的两个最小结点,把新的父节点加入队列


这是对第三步的重复操作,也就是从队列中删除912,插入21,并且仍然保持队列的升序:

640.png

 

 

第八步:选择当前权值最小的两个结点,生成新的父结点


这是对第二步的重复操作。当前队列中权值最小的结点是1821,生成新的父结点权值是18+21=39

640.png


第九步:从队列中移除上一步选择的两个最小结点,把新的父节点加入队列


这是对第三步的重复操作,也就是从队列中删除1821,插入39,并且仍然保持队列的升序:



640.png

 

第十步:选择当前权值最小的两个结点,生成新的父结点


这是对第二步的重复操作。当前队列中权值最小的结点是2539,生成新的父结点权值是25+39=64



640.png

 

 

第十一步:从队列中移除上一步选择的两个最小结点,把新的父节点加入队列


这是对第三步的重复操作,也就是从队列中删除2539,插入64

640.png


此时,队列中仅有一个结点,说明整个森林已经合并成了一颗树,而这棵树就是我们想要的哈夫曼树:

640.png640.jpg640.jpg

private
Node
 root
;
private
Node
[]
 nodes
;
//构建哈夫曼树
public
void
 createHuffman
(
int
[]
 weights
)
{
//优先队列,用于辅助构建哈夫曼树
Queue
<
Node
>
 nodeQueue 
=
new
PriorityQueue
<>();
    nodes 
=
new
Node
[
weights
.
length
];
//构建森林,初始化nodes数组
for
(
int
 i
=
0
;
 i
<
weights
.
length
;
 i
++){
        nodes
[
i
]
=
new
Node
(
weights
[
i
]);
        nodeQueue
.
add
(
nodes
[
i
]);
}
//主循环,当结点队列只剩一个结点时结束
while
(
nodeQueue
.
size
()
>
1
)
{
//从结点队列选择权值最小的两个结点
Node
 left 
=
 nodeQueue
.
poll
();
Node
 right 
=
 nodeQueue
.
poll
();
//创建新结点作为两结点的父节点
Node
 parent 
=
new
Node
(
left
.
weight 
+
 right
.
weight
,
 left
,
 right
);
        nodeQueue
.
add
(
parent
);
}
    root 
=
 nodeQueue
.
poll
();
}
//按照前序遍历输出
public
void
 output
(
Node
 head
)
{
if
(
head 
==
null
){
return
;
}
System
.
out
.
println
(
head
.
weight
);
    output
(
head
.
lChild
);
    output
(
head
.
rChild
);
}
public
static
class
Node
implements
Comparable
<
Node
>{
int
 weight
;
Node
 lChild
;
Node
 rChild
;
public
Node
(
int
 weight
)
{
this
.
weight 
=
 weight
;
}
public
Node
(
int
 weight
,
Node
 lChild
,
Node
 rChild
)
{
this
.
weight 
=
 weight
;
this
.
lChild 
=
 lChild
;
this
.
rChild 
=
 rChild
;
}
@Override
public
int
 compareTo
(
Node
 o
)
{
return
new
Integer
(
this
.
weight
).
compareTo
(
new
Integer
(
o
.
weight
));
}
}
public
static
void
 main
(
String
[]
 args
)
{
int
[]
 weights 
=
{
2
,
3
,
7
,
9
,
18
,
25
};
HuffmanTree
 huffmanTree 
=
new
HuffmanTree
();
    huffmanTree
.
createHuffman
(
weights
);
    huffmanTree
.
output
(
huffmanTree
.
root
);
}

在这段代码中,为了保证结点队列当中的结点始终按照权值升序排列,我们使用了优先队列PriorityQueue

与此同时,静态内部类Node需要实现比较接口,重写compareTo方法,以保证Node对象在进入队列时按照权值来比较。

640.jpg640.jpg640.jpg640.jpg640.jpg640.jpg



相关文章
【剑指offer】-树的子结构-17/67
【剑指offer】-树的子结构-17/67
|
4天前
|
C++
图解哈夫曼树
图解哈夫曼树
|
10月前
1351:【例4-12】家谱树
1351:【例4-12】家谱树
|
11月前
哈夫曼树的基础知识
哈夫曼树的基础知识
56 0
|
11月前
剑指offer 25. 树的子结构
剑指offer 25. 树的子结构
34 0
|
算法
【刷算法】一棵树是否是另一棵树的子结构
【刷算法】一棵树是否是另一棵树的子结构
树好像没有辣么难
之前不会用树处理数据,直到怎么用之后,发现还是很好用的
70 0
【每日算法打卡】100. 相同的树
【每日打卡系列】LeetCode 简单题 200 道
【每日算法打卡】100. 相同的树
漫画:什么是AVL树?(修订版)
而在AVL树当中,我们通过“平衡因子”来判断一颗二叉树是否符合高度平衡。 到底什么是AVL树的平衡因子呢? 对于AVL树的每一个结点,平衡因子是它的左子树高度和右子树高度的差值。只有当二叉树所有结点的平衡因子都是-1, 0, 1这三个值的时候,这颗二叉树才是一颗合格的AVL树。
127 0
漫画:什么是AVL树?(修订版)