讲师中心 微信公众号

工具推荐

视频效率加速
AniShort
AniShort

AI短剧协同制作平台

Laper
Laper

剧本策划到成片协作

立刻MV
立刻MV

低成本歌曲 MV、视频生成

剧云
剧云

中文短剧剧本制作平台

高效构建霍夫曼树:无需优先队列的排序策略

酷浩酱_6857

酷浩酱_6857

发布时间:2025-10-04 19:23:01

|

217人浏览过

|

来源于php中文网

原创

高效构建霍夫曼树:无需优先队列的排序策略

本文详细阐述了一种无需优先队列构建霍夫曼树的高效方法。该策略通过初始排序符号列表,并巧妙地管理两个已排序列表(原始符号和已合并符号),确保每次都能快速选取最小频率的两个节点进行合并,从而实现霍夫曼树的构建,同时保持算法的简洁性和效率。

霍夫曼树简介与传统构建方法

霍夫曼树(huffman tree),又称最优二叉树,是一种带权路径长度最短的二叉树。它在数据压缩领域有着广泛应用,通过为出现频率高的字符分配较短的编码,为频率低的字符分配较长的编码,从而实现整体数据量的缩减。

传统的霍夫曼树构建算法通常依赖于优先队列(Priority Queue)。其基本思想是:

  1. 将所有待编码的字符及其频率作为叶子节点,插入到优先队列中(按频率升序排列)。
  2. 重复以下步骤,直到优先队列中只剩下一个节点:
    • 从优先队列中取出频率最小的两个节点。
    • 创建一个新的内部节点,其频率为这两个节点频率之和,并将这两个节点作为其左右子节点。
    • 将新创建的内部节点插入回优先队列。
  3. 优先队列中最后剩下的那个节点即为霍夫曼树的根节点。

然而,在某些特定场景或教学要求下,可能需要避免使用优先队列来实现这一过程。这要求我们寻找一种替代方案,能够在不直接使用优先队列数据结构的情况下,依然高效地找到并合并频率最小的两个节点。

无需优先队列的霍夫曼树构建算法

在不使用优先队列的情况下构建霍夫曼树,可以采用一种巧妙的“双列表排序策略”。这种方法利用了初始排序和列表合并的特性,模拟了优先队列的行为。

核心思想:双列表排序策略

该策略的核心在于维护两个已排序的列表:

  1. 原始符号列表 (Original Symbols List):存放所有初始的字符节点,按频率升序排列。
  2. 已合并符号列表 (Combined Symbols List):存放所有通过合并操作新生成的内部节点,同样按频率升序排列。

在每次合并操作中,我们只需要从这两个列表的头部(即最小频率的元素)中选取两个最小的节点进行合并。由于新合并的节点的频率总是大于或等于其子节点的频率,且通常大于之前已合并节点的频率,因此可以将新节点直接添加到“已合并符号列表”的末尾,而该列表依然能保持其升序特性。

详细步骤

以下是构建霍夫曼树的具体步骤:

  1. 初始化节点列表:

    • 为每个字符创建一个节点对象,包含字符本身、其频率以及左右子节点指针(初始为null)。
    • 将所有这些初始字符节点收集到一个列表中。
  2. 对原始符号列表进行排序:

    logos-node
    logos-node

    在 Linux x86_64 上安装、更新或检查 Logos 区块链测试网验证节点。用于用户需要搭建 Logos 节点时使用。

    下载
    • 将步骤1中创建的节点列表按照节点的频率进行升序排序。这个列表将作为我们的“原始符号列表”。
  3. 创建空的已合并符号列表:

    • 初始化一个空的列表,用于存放后续合并操作产生的内部节点。这个列表将作为我们的“已合并符号列表”。
  4. 循环合并节点:

    • 当“原始符号列表”和“已合并符号列表”中的节点总数大于1时,重复以下操作:

      • 选取频率最小的两个节点:

        • 比较“原始符号列表”的第一个节点和“已合并符号列表”的第一个节点(如果两个列表都不为空)。
        • 从这两个列表中选出频率最小的节点。
        • 重复此过程,选出第二个频率最小的节点。
        • 选择逻辑:
          • 如果“原始符号列表”为空,则从“已合并符号列表”中取出两个最小的节点。
          • 如果“已合并符号列表”为空,则从“原始符号列表”中取出两个最小的节点。
          • 如果两个列表都不为空:
            • 比较两个列表的头部元素,取出频率较小的那个作为第一个节点。
            • 再次比较剩余的两个列表的头部元素,取出频率较小的那个作为第二个节点。
            • (注意:在取出节点后,要从原列表中移除该节点。)
      • 合并节点:

        • 创建一个新的内部节点。
        • 将选出的两个节点作为新节点的左右子节点。
        • 新节点的频率等于其两个子节点的频率之和。
      • 添加至已合并符号列表:

        • 将新创建的内部节点添加到“已合并符号列表”的末尾。
        • 原理: 由于新节点的频率是两个较小频率之和,它必然大于或等于其子节点的频率。更重要的是,它通常会大于或等于之前已合并到此列表中的任何节点的频率(因为我们总是从最小的开始合并)。因此,将新节点添加到列表末尾,可以保持“已合并符号列表”的升序特性。
  5. 获取霍夫曼树根节点:

    • 当循环结束时,意味着只剩下一个节点。这个节点就是霍夫曼树的根节点。它可能在“原始符号列表”中(如果初始只有一个字符),也可能在“已合并符号列表”中。

示例伪代码

class Node:
    def __init__(self, char, freq, left=None, right=None):
        self.char = char
        self.freq = freq
        self.left = left
        self.right = right

    # 用于比较节点,便于排序
    def __lt__(self, other):
        return self.freq < other.freq

def build_huffman_tree_without_priority_queue(frequencies):
    # 1. 初始化节点列表
    original_nodes = []
    for char, freq in frequencies.items():
        original_nodes.append(Node(char, freq))

    # 2. 对原始符号列表进行排序
    original_nodes.sort()

    # 3. 创建空的已合并符号列表
    combined_nodes = []

    # 辅助函数:从两个列表中选择频率最小的节点
    def get_min_node(list1, list2):
        if not list1:
            return list2.pop(0)
        if not list2:
            return list1.pop(0)

        if list1[0].freq < list2[0].freq:
            return list1.pop(0)
        else:
            return list2.pop(0)

    # 4. 循环合并节点
    while len(original_nodes) + len(combined_nodes) > 1:
        node1 = get_min_node(original_nodes, combined_nodes)
        node2 = get_min_node(original_nodes, combined_nodes)

        # 合并节点
        merged_freq = node1.freq + node2.freq
        merged_node = Node(None, merged_freq, node1, node2) # 内部节点字符为None

        # 添加至已合并符号列表末尾
        combined_nodes.append(merged_node)

        # 保持 combined_nodes 列表的排序(虽然通常是追加,但为了严谨性或处理特殊情况,可以考虑重新排序或插入排序)
        # 实际上,由于新节点的频率总是大于或等于其子节点,且通常大于已合并列表中的其他节点,直接追加即可保持排序。
        # 如果有特殊情况导致不保持,则需要在此处进行插入排序。但对于霍夫曼树,通常无需。
        # 这里为了简化,假设直接追加是有效的,因为新节点频率最大。

    # 5. 获取霍夫曼树根节点
    if original_nodes:
        return original_nodes[0]
    else:
        return combined_nodes[0]

# 示例使用
frequencies = {'a': 5, 'b': 9, 'c': 12, 'd': 13, 'e': 16, 'f': 45}
huffman_root = build_huffman_tree_without_priority_queue(frequencies)

# 打印霍夫曼编码 (需要一个额外的函数来遍历树并生成编码)
def generate_huffman_codes(node, current_code="", codes={}):
    if node is None:
        return
    if node.char is not None: # 是叶子节点
        codes[node.char] = current_code
        return
    generate_huffman_codes(node.left, current_code + "0", codes)
    generate_huffman_codes(node.right, current_code + "1", codes)
    return codes

huffman_codes = generate_huffman_codes(huffman_root)
print("霍夫曼编码:", huffman_codes)

# 预期输出示例 (编码可能因左右子树分配而异,但频率排序是固定的)
# 霍夫曼编码: {'a': '1100', 'b': '1101', 'c': '100', 'd': '101', 'e': '111', 'f': '0'}

算法优势与原理分析

  • 效率: 初始排序的时间复杂度为 O(N log N),其中 N 是字符的数量。随后的合并操作,每次选择两个最小节点并将其添加到 combined_nodes 列表末尾,此过程重复 N-1 次。每次选择操作(比较两个列表的头部)是 O(1),添加操作也是 O(1)。因此,合并阶段的总时间复杂度为 O(N)。整体算法的时间复杂度由初始排序决定,为 O(N log N),与使用优先队列的霍夫曼算法复杂度相同。
  • 简洁性: 避免了优先队列复杂的内部实现,通过简单的列表操作即可完成。
  • 原理: 关键在于新合并的节点频率总是大于或等于其子节点的频率。当我们从两个已排序列表的头部取出最小元素进行合并时,新生成的父节点的频率必然大于或等于这两个被合并子节点的频率。由于我们总是从“现有”的最小节点开始合并,新生成的父节点其频率也必然大于或等于之前已经合并并加入 combined_nodes 列表的节点。因此,将其直接追加到 combined_nodes 列表的末尾,可以保持该列表的升序特性。

注意事项

  1. 节点结构: 确保自定义的节点类包含字符(或标识符)、频率以及左右子节点的引用。
  2. 空列表处理: 在 get_min_node 辅助函数中,需要妥善处理其中一个列表为空的情况,确保算法能够正确地从非空列表中获取节点。
  3. 频率相同时的处理: 如果存在多个节点频率相同,初始排序和后续选择时,它们的相对顺序可能会影响最终霍夫曼树的形态,但不会影响其最优性(即带权路径长度)。如果需要保证霍夫曼树的唯一性(例如,为了测试),可能需要引入第二个排序标准(如字符的ASCII值)。
  4. 实际应用: 构建霍夫曼树只是第一步,后续还需要遍历树来生成每个字符的霍夫曼编码,以及实现编码和解码功能。

总结

通过采用“双列表排序策略”,我们成功地在不使用优先队列的情况下构建了霍夫曼树。这种方法不仅在理论上与传统优先队列方法具有相同的渐进时间复杂度,而且通过巧妙地利用列表的排序特性,提供了一种简洁而高效的实现途径。这对于理解数据结构和算法的底层机制,以及在特定约束下解决问题,都具有重要的启发意义。

热门AI工具

更多
火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

超级麦吉

一款通过数字员工协作完成调研、数据分析与文档制作的AI办公平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

569

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1698

2024.03.01

mysql标识符无效错误怎么解决
mysql标识符无效错误怎么解决

mysql标识符无效错误的解决办法:1、检查标识符是否被其他表或数据库使用;2、检查标识符是否包含特殊字符;3、使用引号包裹标识符;4、使用反引号包裹标识符;5、检查MySQL的配置文件等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

17840

2023.12.04

Python标识符有哪些
Python标识符有哪些

Python标识符有变量标识符、函数标识符、类标识符、模块标识符、下划线开头的标识符、双下划线开头、双下划线结尾的标识符、整型标识符、浮点型标识符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1263

2024.02.23

java标识符合集
java标识符合集

本专题整合了java标识符相关内容,想了解更多详细内容,请阅读下面的文章。

9590

2025.06.11

c++标识符介绍
c++标识符介绍

本专题整合了c++标识符相关内容,阅读专题下面的文章了解更多详细内容。

3822

2025.08.07

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

399

2026.04.23

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2881

2023.12.01

Go-Zero API开发使用教程
Go-Zero API开发使用教程

本专题聚焦Go-Zero的API开发,拆解.api描述文件的语法结构、路由与分组定义、参数绑定与校验规则,说明统一响应体、JWT鉴权、跨域与接口文档生成的落地方式,并给出网关聚合与接口测试的实践建议。

0

2026.11.12

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WebSocket手册
WebSocket手册

共0课时 | 0人学习

HTML5/CSS3/JavaScript/ES6入门课程
HTML5/CSS3/JavaScript/ES6入门课程

共102课时 | 11.1万人学习

前端基础到实战(HTML5+CSS3+ES6+NPM)
前端基础到实战(HTML5+CSS3+ES6+NPM)

共162课时 | 29.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn