目录

树结构

一、Huffman树(最优二叉树)

1、基本概念

2、Huffman树的定义

3、构建哈夫曼算法

4、示例说明

5、结构特点

6、应用:哈夫曼编码

二、哈夫曼编码的轻松介绍

1、哈夫曼编码的定义

2、核心原理

3、关键特性

4、便捷示例

5、应用场景

树结构

一、Huffman树(最优二叉树)

1、基本概念

(1)路径长度:路径上的分支数目

(2)树的路径长度:从树根到每一结点的路径长度之和

(3)带权路径长度:结点到根的路径长度与结点上权的乘积

(4)树的带权路径长度:树中所有叶子结点的带权路径长度之和

2、Huffman树的定义

哈夫曼树(Huffman Tree),又称最优二叉树,是一类带权路径长度(Weighted Path Length, WPL)最短的二叉树,其核心特性是:对于给定的一组带权值的叶子节点,通过特定特定构造方法使树的总带权路径长度最小。哈夫曼树是 “最优树” 的典型代表,广泛应用于数据压缩、编码等领域。

核心概念:

使该值最小。就是(1)带权路径长度(WPL)指树中所有叶子节点的权值与该节点到根节点的路径长度(边的数量)的乘积之和,公式为:WPL=∑i=1n​(wi​×li​)其中,wi​ 是第 i 个叶子节点的权值,li​ 是该叶子节点到根节点的路径长度。哈夫曼树的目标

(2)叶子节点与非叶子节点哈夫曼树中,权值仅存在于叶子节点,非叶子节点的权值由其左右子树的权值之和构成(用于构建树的中间过程)。

3、构建哈夫曼算法

哈夫曼树经过贪心策略构建,步骤如下:

(1)初始化:将所有带权值的叶子节点视为独立的单节点树,组成一个森林(集合)。

(2)迭代合并:

① 从森林中选取权值最小的两棵树,作为新树的左、右子树(左、右顺序不影响 WPL,通常权值小的放左)。

② 新树的根节点权值为两棵子树的权值之和。

③ 将新树加入森林,同时移除参与合并的两棵树。

(3)终止条件:当森林中只剩下一棵树时,该树即为哈夫曼树。

代码示例:

class HuffmanNode:
    """哈夫曼树节点类"""
    def __init__(self, weight, char=None, left=None, right=None):
        self.weight = weight  # 节点权重(叶子节点为字符频率,非叶子节点为子树权重和)
        self.char = char      # 叶子节点对应的字符(非叶子节点为None)
        self.left = left      # 左子树
        self.right = right    # 右子树
    # 定义比较方法,用于优先队列排序(按权重从小到大)
    def __lt__(self, other):
        return self.weight < other.weight
def build_huffman_tree(char_weights):
    """
    构建哈夫曼树
    :param char_weights: 字符-权重字典(如 {'A':5, 'B':9, ...})
    :return: 哈夫曼树的根节点
    """
    import heapq  # 利用堆实现优先队列,高效获取最小权重节点
    # 1. 初始化:将所有字符转为叶子节点,加入优先队列(小根堆)
    heap = []
    for char, weight in char_weights.items():
        node = HuffmanNode(weight, char)
        heapq.heappush(heap, node)
    # 2. 迭代合并:每次取两个最小权重节点,合并为新节点
    while len(heap) > 1:
        # 取出权重最小的两个节点
        left_node = heapq.heappop(heap)
        right_node = heapq.heappop(heap)
        # 构建新节点(权重为两节点之和,无字符)
        merged_weight = left_node.weight + right_node.weight
        merged_node = HuffmanNode(merged_weight, left=left_node, right=right_node)
        # 将新节点加入优先队列
        heapq.heappush(heap, merged_node)
    # 3. 剩余的最后一个节点即为哈夫曼树的根节点
    return heap[0] if heap else None
def calculate_wpl(root, depth=0):
    """计算哈夫曼树的带权路径长度(WPL)"""
    if root is None:
        return 0
    # 叶子节点:累加 权重×深度
    if root.left is None and root.right is None:
        return root.weight * depth
    # 非叶子节点:递归计算左右子树的WPL之和
    return calculate_wpl(root.left, depth + 1) + calculate_wpl(root.right, depth + 1)
# 示例使用
if __name__ == "__main__":
    # 字符及其频率(权重)
    char_weights = {'A': 5, 'B': 9, 'C': 12, 'D': 13, 'E': 16, 'F': 45}
    # 构建哈夫曼树
    huffman_root = build_huffman_tree(char_weights)
    # 计算WPL
    wpl = calculate_wpl(huffman_root)
    print(f"哈夫曼树的带权路径长度(WPL):{wpl}")  # 输出:269(与之前示例一致)

4、示例说明

示例1:给定叶子节点权值:[5, 9, 12, 13, 16, 45],构建哈夫曼树的过程:

(1)初始森林:{5, 9, 12, 13, 16, 45}

(2)第一次合并:选最小的 5 和 9,合并为根权值 14 的树,森林变为 {12, 13, 14, 16, 45}

(3)第二次合并:选 12 和 13,合并为根权值 25 的树,森林变为 {14, 16, 25, 45}

(4)第三次合并:选 14 和 16,合并为根权值 30 的树,森林变为 {25, 30, 45}

(5)第四次合并:选 25 和 30,合并为根权值 55 的树,森林变为 {45, 55}

(6)第五次合并:选 45 和 55,合并为根权值 100 的树,森林只剩一棵树,即哈夫曼树。

最终树的 WPL 计算:5×4+9×4+12×3+13×3+16×3+45×2=20+36+36+39+48+90=269,为所有可能二叉树中最小的 WPL。

示例2:

5、结构特点

(1)权值越大的叶子节点,距离根节点越近(贪心策略的体现,减少高权值节点的路径长度)。

(2)没有度为 1 的节点(所有非叶子节点均有左右子树,即 “严格二叉树”)。

(3)若叶子节点数量为 n,则哈夫曼树的总节点数为 2n−1(因每次合并增加 1 个节点,共需 n−1 次合并)。

6、应用:哈夫曼编码

哈夫曼树的典型应用是哈夫曼编码(数据压缩算法),原理如下:

(1)对字符(叶子节点)按出现频率(权值)构建哈夫曼树。

(2)从根到叶子的路径中,左分支标记为 “0”,右分支标记为 “1”,叶子节点的路径编码即为该字符的哈夫曼编码。

(3)特性:高频字符编码短,低频字符编码长,且编码无歧义(前缀编码,任一编码不是其他编码的前缀),从而实现高效压缩。

二、哈夫曼编码的简单介绍

1、哈夫曼编码的定义

哈夫曼编码是基于哈夫曼树(最优二叉树)的一种前缀编码,核心是为高频字符分配短编码、低频字符分配长编码,从而实现信息的高效压缩,且无编码歧义。

2、核心原理

(1)以频率为权值构建哈夫曼树将得编码的字符视为 “叶子节点”,其出现频率作为节点 “权值”,按哈夫曼树的构建规则(反复合并权值最小的两棵树)生成哈夫曼树。

(2)路径即编码从哈夫曼树的根节点出发,向左子树走标记为 “0”,向右子树走标记为 “1”;每个叶子节点(字符)从根到自身的路径序列,就是该字符的哈夫曼编码。

3、关键特性

(1)前缀编码:任一字符的编码都不是其他字符编码的前缀,避免解码时产生歧义。例如 “01” 和 “010” 不能同时存在,基于前者是后者的前缀。

(2)压缩高效:高频字符编码短、低频字符编码长,整体编码总长度最短,压缩率高于固定长度编码(如 ASCII 码)。

4、简单示例

假设字符及出现频率如下:

字符ABCD
频率591213

(1)构建哈夫曼树:按频率(权值)合并,最终生成哈夫曼树(过程参考哈夫曼树构建)。

(2)生成编码:

① A(频率 5):路径为 “000”,编码 = 000

② B(频率 9):路径为 “001”,编码 = 001

③ C(频率 12):路径为 “01”,编码 = 01

④ D(频率 13):路径为 “1”,编码 = 1

(3)编码与解码:原文 “ABDC” 的编码为 “000 001 1 01”(合并后为 “000001101”);解码时,从根出发按 “0/1” 路径找叶子节点,即可还原为原字符。

5、应用场景

无损数据压缩的基础,广泛用于:就是哈夫曼编码

(1)材料压缩格式(如 ZIP、GZIP)。

(2)图像压缩(如 JPEG 的 entropy coding 阶段)。

(3)通信传输中的数据编码,减少传输带宽。