哈夫曼树构建与编码生成有五种C++实现方案:一、优先队列贪心法;二、vector+make_heap手动堆管理;三、静态数组模拟堆;四、set有序集合替代堆;五、双栈迭代生成编码。

如果需要在C++中构建哈夫曼树并生成最优二进制编码,则必须基于字符频率构造带权路径长度最小的二叉树。以下是实现该算法核心逻辑的多种源码方案:
一、基于优先队列(最小堆)的标准贪心实现
该方案利用std::priority_queue维护节点权重升序,每次取出两个权值最小的子树合并为新节点,符合哈夫曼算法贪心选择性质。时间复杂度为O(n log n),空间复杂度为O(n)。
1、定义哈夫曼树节点结构体,包含字符、频次、左右子指针,并重载小于运算符以支持最小堆排序。
2、将所有叶节点(字符及对应频次)压入优先队列。
立即学习“C++免费学习笔记(深入)”;
3、当队列中节点数大于1时,连续弹出两个最小权值节点,构造新父节点,权值为二者之和,左子为first,右子为second。
4、将新父节点重新入队。
5、队列中剩余唯一节点即为哈夫曼树根节点。
6、从根出发递归遍历,向左走记0、向右走记1,到达叶子时保存字符→编码字符串映射关系。
二、使用vector+make_heap的手动堆管理实现
该方案避免依赖std::priority_queue的黑盒行为,显式使用vector存储节点指针,并通过std::make_heap、std::pop_heap、std::push_heap控制堆操作,便于调试与内存跟踪。
1、初始化vector
2、调用std::make_heap(vec.begin(), vec.end(), cmp),其中cmp为按freq升序的比较函数对象。
3、循环执行:调用std::pop_heap(vec.begin(), vec.end(), cmp)将最小元素移至末尾,再取末尾两元素合并。
4、将新节点push_back后,再次调用std::push_heap保持堆性质。
5、最终vec[0]即为根节点指针,后续编码生成方式与方案一相同。
6、所有动态分配的Node对象需在析构阶段统一delete,防止内存泄漏。
三、静态数组模拟堆的无动态分配实现
该方案适用于嵌入式或内存受限环境,预先分配固定大小数组(如256个ASCII字符最多生成511个节点),用数组下标模拟父子关系,完全规避new/delete调用。
1、声明Node heap[MAX_NODES],初始将前n个位置填入叶节点,其余置空。
2、实现heapify_down函数,依据索引i计算left=2*i+1、right=2*i+2,比较并交换以维持最小堆。
3、实现extract_min:取heap[0],用末尾非空节点覆盖heap[0],再heapify_down(0)。
4、每次合并后将新节点存入下一个空闲索引位,并更新堆大小计数器。
5、构建完成后,通过parent数组反向追溯路径,生成编码时需从叶子向上记录比特序列再反转。
四、STL set替代堆的有序集合实现
该方案利用std::set自动排序特性,以pair
1、定义set类型为std::set<:pair node>, std::less>,注意自定义比较仅依据first(weight)。
2、插入所有叶节点,每个pair.first为频次,second为指向该节点的裸指针。
3、取begin()两次获得最小两个元素,保存其Node*,从set中erase。
4、构造新Node,其weight为两者之和,左右子指针分别赋值,再insert新pair。
5、重复直至set.size() == 1。
6、因set不保证指针稳定性,所有Node必须在外部统一管理生命周期,避免悬空指针解引用。
五、编码生成阶段的双栈迭代非递归实现
该方案规避递归调用栈开销与潜在栈溢出风险,使用两个stack:一个存节点指针,一个存对应路径编码字符串,同步压栈/弹栈。
1、初始化:根节点入栈ptr_stack,空字符串入栈code_stack。
2、while ptr_stack非空:弹出当前节点cur和当前编码cur_code。
3、若cur为叶子(left==nullptr && right==nullptr),则将cur->ch与cur_code存入map。
4、否则,若cur->right非空,将cur->right与cur_code+"1"压入对应栈;同理处理cur->left与cur_code+"0"。
5、压栈顺序必须为先右后左,确保左子树编码字典序在前,最终结果与递归一致。
6、此过程无需递归,全程使用常量级额外栈空间(除输出映射外)。


















