数据结构之树(Huffman tree(赫夫曼树 / 霍夫曼树 / 哈夫曼树 / 最优二叉树))

发布时间 2023-10-31 00:29:22作者: Allen_Hao

赫夫曼树概述

HuffmanTree因翻译不同导致其有多个名字:赫夫曼树、霍夫曼树、哈夫曼树

赫夫曼树又称最优二叉树,是一种带权路径长度 最短的二叉树。

所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。

树的路径长度是从树根到每一结点的路径长度之和,记为WPL=(W1L1+W2L2+W3L3+…+WnLn),N个权值Wi(i=1,2,…n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,…n)。可以证明赫夫曼树的WPL是最小的。

术语

1. 路径

路径是指从一个节点到另一个节点的分支序列。

2. 路径长度

指从一个节点到另一个结点所经过的分支数目。 如下图:从根节点到a的分支数目为2

 

3. 树的路径长度

树中所有结点的路径长度之和为树的路径长度PL。 如下图:PL为10

根节点到A的父节点为1

根节点到C的父节点为1

根节点到A节点为2

根节点到B节点为2

根节点到C节点为2

根节点到D节点为2

 

4. 节点的权

给树的每个结点赋予一个具有某种实际意义的实数,我们称该实数为这个结点的权。如下图:7、5、2、4

 

5. 带权路径长度

从树根到某一结点的路径长度与该节点的权的乘积,叫做该结点的带权路径长度。如下图:A的带权路径长度为2*7=14

 

6. 树的带权路径长度(WPL)

树的带权路径长度为树中所有叶子节点的带权路径长度之和

 

7. 最优二叉树

权值最大的节点离根节点越近的二叉树,所得WPL值最小,就是最优二叉树。如下图:(b)A节点权值最大(为9)且离根节点最近

 

 

  • (a)WPL=9*2+4*2+5*2+2*2=40
  • (b)WPL=9*1+5*2+4*3+2*3=37

构造赫夫曼树步骤

对于数组{5,29,7,8,14,23,3,11},把它构造成赫夫曼树。

第1步:使用数组中所有元素创建若干个二叉树节点,这些值作为节点的权值。

 

第2步:将这些节点按照权值的大小进行排序

 

第3步:取出权值最小的两个节点,并创建一个新的节点作为这两个节点的父节点,这个父节点的权值为两个子节点的权值之和。将这两个节点分别赋给父节点的左右节点。

 第4步:删除这两个节点,将父节点添加进集合里

 

第5步:重复第二步到第四步,直到集合中只剩一个元素,结束循环

 

代码实现

 1 import java.util.ArrayList;
 2 import java.util.Collections;
 3 import java.util.List;
 4 
 5 /**
 6  * 霍夫曼树
 7  */
 8 public class TestHuffmanTree {
 9     public static void main(String[] args) {
10         int[] arr = {5, 29, 7, 8, 14, 23, 3, 11};
11         Node node = createHuffmanTree(arr);
12         System.out.println(node); //Node value=100
13     }
14 
15     //创建赫夫曼树
16     public static Node createHuffmanTree(int[] arr) {
17         //使用数组中所有元素创建若干个二叉树(只有一个节点)
18         List<Node> nodes = new ArrayList<>();
19         for (int value : arr) {
20             nodes.add(new Node(value));
21         }
22 
23         //循环处理
24         while (nodes.size() > 1) {
25             //排序
26             Collections.sort(nodes);
27             //取出最小的两个二叉树(集合为倒叙,从大到小)
28             Node left = nodes.get(nodes.size() - 1); //权值最小
29             Node right = nodes.get(nodes.size() - 2); //权值次小
30             //创建一个新的二叉树
31             Node parent = new Node(left.value + right.value);
32             //删除原来的两个节点
33             nodes.remove(left);
34             nodes.remove(right);
35             //新的二叉树放入原来的二叉树集合中
36             nodes.add(parent);
37             //打印结果
38             System.out.println(nodes);
39         }
40         return nodes.get(0);
41     }
42 }
43 
44 //接口实现排序功能
45 class Node implements Comparable<Node> {
46     int value;
47     Node left;
48     Node right;
49 
50     public Node(int value) {
51         this.value = value;
52     }
53 
54     @Override
55     public int compareTo(Node o) {
56         return -(this.value - o.value); //集合倒叙,从大到小
57     }
58 
59     @Override
60     public String toString() {
61         return "Node value=" + value;
62     }
63 }

输出:

[Node value=29, Node value=23, Node value=14, Node value=11, Node value=8, Node value=7, Node value=8]
[Node value=29, Node value=23, Node value=14, Node value=11, Node value=8, Node value=15]
[Node value=29, Node value=23, Node value=15, Node value=14, Node value=19]
[Node value=29, Node value=23, Node value=19, Node value=29]
[Node value=29, Node value=29, Node value=42]
[Node value=42, Node value=58]
[Node value=100]
Node value=100

  

应用场景

赫夫曼树的典型应用场景是在数据压缩和编码中,尤其是在通信和存储领域。以下是赫夫曼树的典型应用场景:

  1. 数据压缩:赫夫曼树广泛用于数据压缩算法,例如在ZIP、Gzip、JPEG、MP3等压缩文件格式中。数据压缩通过使用赫夫曼编码来减小数据的体积,从而节省存储空间和提高数据传输效率。频率高的字符或数据被编码为较短的比特串,而频率低的字符或数据被编码为较长的比特串。

  2. 通信传输:在通信领域,赫夫曼编码可用于压缩数据以减小传输带宽的需求。这在网络通信、移动通信和卫星通信中非常重要,因为它可以减少数据传输的成本并提高传输速度。HTTPS通信中的数据也经常使用赫夫曼编码进行加密和压缩。

  3. 图像压缩:JPEG图像压缩标准使用了一种称为"霍夫曼熵编码"的方法,其中赫夫曼编码用于压缩图像数据,减小图像文件的大小,同时保持图像质量。

  4. 音频压缩:音频压缩算法如MP3也使用赫夫曼编码来减小音频文件的大小,同时保持音质。这使得音频文件更适合在互联网上流式传输和存储。

  5. 文件压缩:文件压缩工具如ZIP和Gzip使用赫夫曼编码来压缩多种类型的文件。这些工具可以将多个文件或文件夹打包成一个单一的压缩文件,以节省存储空间和方便文件传输。

  6. 数据存储:在一些数据库管理系统中,赫夫曼编码用于优化数据的存储和检索。赫夫曼编码可以减小存储数据所需的空间,同时提高数据检索的效率。

  7. 数据传输的纠错:在一些通信协议中,赫夫曼编码还可以用于检测和纠正传输中的错误。这有助于提高数据传输的可靠性。

总之,赫夫曼树和赫夫曼编码在多个应用领域中都发挥了关键作用,减小了数据传输和存储的开销,提高了效率,并保持了数据的完整性和质量。