哈夫曼树构建采用贪心策略:用最小堆每次选两个最小权值节点合并,直至剩根节点;通过自定义比较器实现priority_queue最小堆;递归遍历生成0/1编码;亦可用vector手动模拟堆;需先统计字符频次并初始化叶子节点。

一、构建哈夫曼树的优先队列贪心策略
哈夫曼编码的核心在于利用贪心思想,每次从当前所有节点中选取权值最小的两个节点合并为新节点,直至只剩一个根节点。该过程依赖最小堆(优先队列)高效获取最小权值节点。C++标准库中的priority_queue默认为最大堆,需通过自定义比较器构造最小堆。
1、定义节点结构体,包含字符、权值、左右子节点指针;
2、重载小于运算符,使priority_queue按权值升序排列;
3、将所有叶子节点入队;
4、循环执行:弹出两个最小权值节点,创建新内部节点,权值为二者之和,并将新节点入队;
5、当队列中仅剩一个节点时,该节点即为哈夫曼树根节点。
二、使用std::priority_queue实现最小堆封装
为避免手动维护堆结构,采用std::priority_queue配合自定义仿函数实现最小堆。关键点在于比较逻辑必须反向定义——即让top()返回最小元素,需使用greater或lambda表达式反转默认行为。
1、声明优先队列类型:priority_queue
2、定义cmp为lambda:auto cmp = [](Node* a, Node* b) { return a->weight > b->weight; };
3、确保每个Node对象在堆中独立分配内存,避免悬空指针;
4、合并过程中动态new Node,权值设为左右子节点权值之和;
5、最终返回pq.top()作为根节点指针。
三、递归遍历生成最优二进制编码字符串
哈夫曼树构建完成后,需从根节点出发深度优先遍历,向左子树走记为'0',向右子树走记为'1',到达叶子节点时记录完整编码。该路径长度即为对应字符的编码长度,满足带权路径长度最小化目标。
1、编写递归函数void generateCodes(Node* root, string code, map
2、若当前节点为叶子节点(left == nullptr && right == nullptr),则将code存入映射表;
3、若存在左子节点,递归调用并追加'0';
4、若存在右子节点,递归调用并追加'1';
5、初始调用传入空字符串作为起始编码。
四、基于vector与索引模拟堆的手动实现方案
当需完全控制堆行为或规避STL分配开销时,可采用vector存储节点指针,并手动实现上浮(sift up)与下沉(sift down)操作。此方式不依赖仿函数,直接在数组内完成最小堆维护。
1、初始化vector
2、对heap执行make_heap(heap.begin(), heap.end(), [](Node* a, Node* b) { return a->weight > b->weight; });
3、每次pop时调用pop_heap,再erase末尾元素;
4、插入新节点后调用push_heap;
5、合并结束后,heap[0]即为根节点指针。
五、字符频次统计与节点初始化预处理
哈夫曼算法输入为各字符出现频次,需先扫描原始数据构建频率映射表,再据此生成初始叶子节点集合。该步骤直接影响后续贪心合并的起点,必须保证每个字符至少对应一个非空节点。
1、使用std::map
2、遍历map,对每对
3、若输入为空字符串,则需特殊处理,返回空根节点并跳过合并流程;
4、若仅有一个唯一字符,则直接创建单节点树,编码为空字符串或全0序列;
5、所有Node指针加入优先队列或vector堆容器,准备进入贪心合并阶段。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











