核心概念解析如下:
路径长度:指从一个结点到另一个结点之间所经过的边(分支)的数量。例如,从根结点到某一叶子结点的路径长度就是该路径上分支的总数。
树的路径长度:定义为从树根到每一个叶子结点的路径长度之和。它反映了整棵树的“平均深度”或结构紧凑性。
带权路径长度(WPL, Weighted Path Length):是哈夫曼树中的关键指标,计算公式为:
WPL=∑k=1nwk⋅lk \text{WPL} = \sum_{k=1}^{n} w_k \cdot l_kWPL=k=1∑nwk⋅lk
其中wkw_kwk是第kkk个叶子结点的权值,lkl_klk是该叶子到根结点的路径长度。WPL 越小,说明高频(大权值)节点越靠近根,整体效率越高。
哈夫曼树:给定nnn个具有确定权值的叶子结点,构造出的所有可能的二叉树中,WPL 最小的那个称为哈夫曼树(最优二叉树)。常用于数据压缩编码(如哈夫曼编码)。
示例说明分析(图 3-23)
假设四个叶子结点的权值分别为 2、4、5、7:
图 (a):若所有叶子都在同一层(比如深度均为 2),则
WPL=2×(2+4+5+7)=2×18=36 \text{WPL} = 2×(2+4+5+7) = 2×18 = 36WPL=2×(2+4+5+7)=2×18=36
(注意原描述有误,“2×(2+4+5+7)”应解释为每个路径长度为2时加权求和)图 (b):构造出的哈夫曼树 WPL 更优:
假设组合过程合理,得到结果:
WPL=3×6+10+7=35(实际需根据具体结构验证) \text{WPL} = 3×6 + 10 + 7 = 35 \quad \text{(实际需根据具体结构验证)}WPL=3×6+10+7=35(实际需根据具体结构验证)
此为最小 WPL,故图(b)为哈夫曼树。图 ©:结构更不平衡,导致较大权值节点更深,WPL 达到 43,性能最差。
结论:不同结构影响 WPL,哈夫曼算法旨在找出最优结构。
哈夫曼树构造算法步骤详解:
- 初始化:将每个权值作为一个独立的单结点二叉树,形成森林FFF。
- 选取与合并:在FFF中选出两棵根结点权值最小的树,合并成一棵新二叉树,新根的权值等于左右子树根权值之和。
- 更新集合:删除原来的两棵树,加入新树。
- 重复:直到FFF中只剩一棵树 —— 即为哈夫曼树。
示例:权值 [2, 4, 5, 7]
第一步:选 2 和 4 → 合并为 6
森林变为:[5, 6, 7]
第二步:选 5 和 6 → 合并为 11
森林变为:[7, 11]
第三步:7 和 11 → 合并为 18
得到最终哈夫曼树,总 WPL 可计算为最小值。
补充特征说明:
- 哈夫曼树不唯一:由于每次选择最小的两棵树时,左右子树可以互换位置,因此结构可能不同,但 WPL 值唯一。
- 结点总数公式:对于nnn个叶子结点的哈夫曼树,总共会有m=2n−1m = 2n - 1m=2n−1个结点(因为每合并一次减少一棵树,共需n−1n-1n−1次合并,产生n−1n-1n−1个内部结点)。
- 存储方式:由于其静态特性,常用一维数组(如结构体数组)实现,记录父节点和子节点下标,适合非递归处理。
# Python 示例:哈夫曼树结点类classHuffmanNode:def__init__(self,weight,left=None,right=None):self.weight=weight self.left=left self.right=rightdefbuild_huffman_tree(weights):fromheapqimportheapify,heappop,heappush# 构建最小堆(Python heapq 是最小堆)nodes=[HuffmanNode(w)forwinweights]heapify(nodes,key=lambdax:x.weight)# 实际使用需自定义比较或包装whilelen(nodes)>1:a=heappop(nodes)b=heappop(nodes)merged=HuffmanNode(a.weight+b.weight,left=a,right=b)heappush(merged)returnheappop(nodes)# 返回根节点注意:上述代码为示意,实际需对 heapq 进行适当封装以支持对象排序。
根据给定的权值序列手动构造哈夫曼树并计算其带权路径长度(WPL),可以按照以下系统步骤进行:
✅步骤一:列出所有叶子结点的权值
假设给定权值序列:[2, 4, 5, 7]
我们将每个权值视为一棵仅含根结点的二叉树。
初始森林 F = {2, 4, 5, 7}
✅步骤二:重复合并最小两棵树,直到只剩一棵树
第1轮:
- 选两个最小的权值:2 和 4
- 合并为新树,根权值 = 2 + 4 =6
- 删除 2 和 4,加入 6
→ 森林变为:{5, 6, 7}
第2轮:
- 选最小两个:5 和 6
- 合并为新树,根权值 = 5 + 6 =11
- 删除 5 和 6,加入 11
→ 森林变为:{7, 11}
第3轮:
- 选最小两个:7 和 11
- 合并为新树,根权值 = 7 + 11 =18
- 删除 7 和 11,加入 18
→ 森林变为:{18},结束
最终得到的这棵根为 18 的二叉树就是哈夫曼树。
✅步骤三:画出哈夫曼树结构(辅助理解)
18 / \ 7 11 / \ 5 6 / \ 2 4注:左右子树可交换,不影响 WPL。
✅步骤四:计算 WPL(带权路径长度)
WPL = 所有叶子结点的权值 × 到根的路径长度之和
逐个看叶子:
| 叶子权值 | 路径长度(从根到该叶的边数) | 贡献 |
|---|---|---|
| 7 | 1 | 7×1 = 7 |
| 5 | 2 | 5×2 = 10 |
| 2 | 3 | 2×3 = 6 |
| 4 | 3 | 4×3 = 12 |
→
WPL=7+10+6+12=35 \text{WPL} = 7 + 10 + 6 + 12 = \boxed{35}WPL=7+10+6+12=35
这是所有可能二叉树中 WPL 的最小值。
✅小结:手动构造哈夫曼树流程图解法
- 将权值从小到大排序;
- 每次取前两个最小的合并,生成父节点;
- 插入回列表并重新排序;
- 重复 n−1 次(n 是叶子数);
- 最后画出树形结构,计算各叶子深度 × 权值,求和得 WPL。
✅技巧提示:
- 不需要真正“画图”也能算 WPL:记录每个原始权值被合并了多少次 —— 实际上每合并一次,其所在子树的所有叶子路径长度都会增加 1。
- 因此,也可以通过合并次数统计深度来间接计算。
例如:
- 权值 2:参与了 3 次合并(第一次成6,第二次成11,第三次成18)→ 深度=3
- 权值 4:同理 → 深度=3
- 权值 5:参与两次后续合并 → 深度=2
- 权值 7:只在最后一次合并 → 深度=1
结果一致。