使用广度优先搜索(BFS)从Python字典中按层级提取数据

使用广度优先搜索(BFS)从Python字典中按层级提取数据

本文探讨如何利用Python的广度优先搜索(BFS)算法,从一个嵌套字典中,根据起始列表和目标列表,按迭代层级提取数据。我们将详细介绍BFS的原理及其在处理此类图结构问题中的应用,并提供两种实现方式,确保高效且结构化地获取期望的输出。

1. 问题背景与目标

在处理复杂数据结构时,我们常会遇到需要从一个具有层级或图状关系的字典中,根据特定规则提取信息的情况。假设我们有一个表示有向图的字典my_dict,其中键是节点,值是其直接邻居节点列表。我们还定义了一个source_list作为起始节点集,以及一个target_list作为终止节点集。我们的目标是,从source_list中的每个节点开始,逐层遍历my_dict,直到遇到target_list中的任一节点为止。同时,我们希望将每次遍历层级(迭代)所发现的节点及其邻居组织成一个字典,最终输出一个以迭代次数为键的嵌套字典。

例如,给定以下数据:

source_list = ['a', 'b']target_list = ['x', 'y', 'z']my_dict = {    'a': ['e'],    'b': ['f', 'd'],    'e': ['g'],    'f': ['t', 'h'],    'd': ['x'],    'g': ['x'],    't': ['y'],    'h': ['z']}

期望的输出是:

{0: {'a': ['e'], 'b': ['f', 'd']}, 1: {'e': ['g'], 'f': ['t', 'h'], 'd': ['x']}, 2: {'g': ['x'], 't': ['y'], 'h': ['z']}}

这里,键0代表第一层迭代,包含从source_list直接可达的节点及其邻居;键1代表第二层迭代,包含从第一层节点可达的节点及其邻居,以此类推。

2. 为什么选择广度优先搜索(BFS)?

最初尝试的解决方案可能使用简单的循环结构,但往往难以正确地管理层级关系并按期望的迭代次数组织输出。这种按层级(或深度)遍历数据结构的需求,正是广度优先搜索(BFS)算法的典型应用场景。

立即学习“Python免费学习笔记(深入)”;

BFS是一种用于遍历或搜索树或图的算法。它从图的某个节点开始,首先访问其所有邻居节点,然后访问这些邻居节点的邻居,依此类推。换句话说,它会先访问距离起始节点“最近”的所有节点,然后再访问距离次之的节点,确保了按层级(或迭代)进行探索。这与我们的需求完美契合,因为我们需要精确地记录每一层迭代所发现的节点。

3. 基于BFS的解决方案实现

我们将介绍两种基于BFS的实现方式。

3.1 基础BFS实现

此实现使用collections.deque作为队列,以高效地管理待访问节点。它通过在队列中存储(level, node)元组来跟踪当前节点的层级。

from collections import dequedef bfs_fetch_by_level(source_nodes, target_nodes, graph_dict):    """    使用广度优先搜索从字典中按层级提取数据。    Args:        source_nodes (list): 起始节点列表。        target_nodes (list): 目标节点列表。        graph_dict (dict): 表示图结构的字典,键为节点,值为其邻居列表。    Returns:        dict: 按层级组织的提取结果字典。    """    queue = deque((0, node) for node in source_nodes) # 队列存储 (层级, 节点)    target_set = set(target_nodes) # 目标节点集合,用于快速查找    seen = set(source_nodes) # 已访问节点集合,防止重复访问和循环    result = {} # 存储最终结果    while queue:        level, current_node = queue.popleft() # 取出当前层级和节点        # 获取当前节点的邻居,如果不存在则为空列表        neighbors = graph_dict.get(current_node, [])        # 将当前节点及其邻居添加到结果字典的对应层级中        result.setdefault(level, {})[current_node] = neighbors[:] # 使用[:]进行浅拷贝,避免修改原始列表        for neighbor in neighbors:            # 如果邻居节点已访问过或在目标列表中,则跳过            # 如果在目标列表中,我们不希望继续探索其子节点,因为已达到目标            if neighbor in seen or neighbor in target_set:                continue            seen.add(neighbor) # 标记为已访问            queue.append((level + 1, neighbor)) # 将邻居加入队列,层级加1    return result# 示例使用source_list = ['a', 'b']target_list = ['x', 'y', 'z']my_dict = {    'a': ['e'],    'b': ['f', 'd'],    'e': ['g'],    'f': ['t', 'h'],    'd': ['x'],    'g': ['x'],    't': ['y'],    'h': ['z']}output_bfs = bfs_fetch_by_level(source_list, target_list, my_dict)print(output_bfs)

输出:

{0: {'a': ['e'], 'b': ['f', 'd']}, 1: {'e': ['g'], 'f': ['t', 'h'], 'd': ['x']}, 2: {'g': ['x'], 't': ['y'], 'h': ['z']}}

代码解析:

deque初始化: 队列中存储的是(层级, 节点)元组。起始节点都在第0层。target_set与seen: target_set用于快速判断一个节点是否为目标节点。seen集合用于记录已访问过的节点,防止重复处理和陷入图中的循环。while queue循环: BFS的核心循环,当队列非空时持续进行。result.setdefault(level, {})[current_node] = neighbors[:]: 这行代码巧妙地构建了输出。setdefault(level, {})确保result字典中存在当前level的键,并将其值初始化为一个空字典(如果不存在)。然后,将current_node作为键,其邻居列表作为值添加到这个内部字典中。使用neighbors[:]创建邻居列表的浅拷贝,避免原始graph_dict的意外修改。邻居遍历与条件判断: 对于每个邻居,我们检查它是否已经访问过 (neighbor in seen) 或者它是否是目标节点 (neighbor in target_set)。如果满足任一条件,我们就不再深入探索这个邻居,因为:如果已访问,继续探索会形成循环或重复路径。如果是目标节点,我们已达到该路径的终点,无需再将其子节点加入队列。queue.append((level + 1, neighbor)): 将未访问且非目标节点的邻居加入队列,并将其层级设置为当前层级加一。

3.2 优化层级构建的BFS实现

第二种实现方式在构建每一层结果时略有不同,它通过一个内部循环来确保当前层的所有节点都被处理完毕,然后才递增层级。这种方式可能在某些情况下更清晰地表达层级概念。

from collections import dequedef build_level_dict(graph, queue, seen, target_set):    """    辅助函数:构建当前层级的字典。    """    # 记录当前层级的最后一个节点,用于判断何时结束本层处理    current_level_end_node = queue[-1] if queue else None     level_dict = {}    while True:        node = queue.popleft()        neighbors = graph.get(node, [])        level_dict[node] = neighbors[:]        for neighbor in neighbors:            if neighbor in seen or neighbor in target_set:                continue            seen.add(neighbor)            queue.append(neighbor)        if node == current_level_end_node: # 当前层所有节点已处理完毕            return level_dictdef optimized_bfs_fetch_by_level(source_nodes, target_nodes, graph_dict):    """    优化版广度优先搜索,按层级提取数据。    """    target_set = set(target_nodes)    result = {}    # 初始已访问节点包含源节点    seen = set(source_nodes)     queue = deque(source_nodes) # 队列只存储节点,层级通过外部循环管理    level = 0    while queue:        # 调用辅助函数构建当前层级的结果        result[level] = build_level_dict(graph_dict, queue, seen, target_set)        level += 1 # 层级递增    return result# 示例使用source_list = ['a', 'b']target_list = ['x', 'y', 'z']my_dict = {    'a': ['e'],    'b': ['f', 'd'],    'e': ['g'],    'f': ['t', 'h'],    'd': ['x'],    'g': ['x'],    't': ['y'],    'h': ['z']}output_optimized_bfs = optimized_bfs_fetch_by_level(source_list, target_list, my_dict)print(output_optimized_bfs)

输出:

{0: {'a': ['e'], 'b': ['f', 'd']}, 1: {'e': ['g'], 'f': ['t', 'h'], 'd': ['x']}, 2: {'g': ['x'], 't': ['y'], 'h': ['z']}}

代码解析:

queue初始化: 队列中只存储节点,不再存储层级元组。seen初始化: 在开始时就将source_nodes加入seen,表示这些节点已“访问”或“处理”,避免重复从它们开始。build_level_dict函数: 这是核心优化点。它接收graph、queue、seen和target_set。current_level_end_node = queue[-1]:在处理当前层级之前,记录队列中最后一个节点。这样,当popleft()取出的节点是这个current_level_end_node时,就意味着当前层的所有节点都已处理完毕。内部while True循环:持续从队列中取出节点,构建level_dict,并将其邻居加入队列。if node == current_level_end_node: return level_dict:当处理到当前层的最后一个节点时,返回构建好的level_dict。optimized_bfs_fetch_by_level主函数: 外部while queue循环负责管理层级level,每次循环调用build_level_dict来构建当前层的结果。

4. 注意事项与总结

图的表示: 这里的my_dict本质上是一个邻接列表表示的图。键是节点,值是其直接可达的邻居节点列表。deque的优势: collections.deque(双端队列)相比于普通Python列表,在两端添加和删除元素(如popleft())时具有O(1)的时间复杂度,这对于BFS算法的性能至关重要。seen集合的重要性: seen集合是防止无限循环和重复计算的关键,尤其是在处理可能包含循环的图时。如果您的my_dict保证是一个树结构(无循环),那么seen集合可以简化或移除,但通常保留它更为安全。target_set: 将target_nodes转换为set可以使查找操作(neighbor in target_set)的平均时间复杂度从O(N)降低到O(1),提高效率。浅拷贝neighbors[:]: 在将邻居列表赋值给结果字典时,使用[:]进行浅拷贝是一个好习惯,可以避免在后续操作中无意修改原始graph_dict中的列表。算法复杂度: BFS的时间复杂度通常是O(V + E),其中V是图中的顶点数,E是边数。空间复杂度是O(V),用于存储队列和seen集合。

通过这两种基于广度优先搜索的实现,我们能够有效地从复杂的嵌套字典结构中,按照指定的起始节点和目标节点,按层级迭代地提取所需数据,并以清晰的结构化格式呈现。这种方法不仅适用于本例中的特定场景,也广泛应用于各种图遍历和最短路径查找问题。

以上就是使用广度优先搜索(BFS)从Python字典中按层级提取数据的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1375833.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月14日 15:22:20
下一篇 2025年12月14日 15:22:28

相关推荐

  • python Paramiko的SSH用法

    Paramiko是Python中实现SSH协议的库,用于自动化远程服务器管理。首先通过pip install paramiko安装;然后使用SSHClient创建连接,可基于用户名密码或私钥认证连接远程主机;执行命令用exec_command获取stdin、stdout、stderr三个通道,输出需…

    2025年12月14日
    000
  • Python 中基于广度优先搜索 (BFS) 的多层级字典数据提取教程

    本文详细介绍了如何使用 Python 的广度优先搜索 (BFS) 算法来遍历和提取嵌套字典中的数据。针对给定起始节点列表和目标节点列表,我们将学习如何按层级(迭代)从字典中抽取相关键值对,直到路径遇到目标节点。教程将提供两种 BFS 实现方案,包括一种优化版本,并深入探讨如何处理图中的循环以及高效利…

    2025年12月14日
    000
  • Python编程教程:修复游戏循环中的类型转换陷阱

    本文深入探讨了Python中while循环的一个常见陷阱:因变量类型动态变化导致的循环提前终止。通过分析一个经典的“石头剪刀布”游戏示例,我们揭示了布尔值与字符串类型转换如何影响循环条件,并提供了一个使用while True结合break语句的健壮解决方案,同时优化了游戏状态重置逻辑,确保游戏能够正…

    2025年12月14日
    000
  • Python while循环陷阱:游戏重玩机制的正确实现

    本文深入探讨了Python中while循环的一个常见陷阱,即变量类型在循环内部被意外修改,导致循环条件失效。通过分析一个“石头剪刀布”游戏的重玩机制问题,文章演示了如何将循环条件从依赖动态变量改为while True,并结合break语句实现精确的循环控制,确保游戏能够正确地重复进行。 理解问题:w…

    2025年12月14日
    000
  • PySpark中使用XPath从XML字符串提取数据的正确指南

    在使用PySpark的xpath函数从XML字符串中提取数据时,开发者常遇到提取节点文本内容时返回空值数组的问题。本文将深入解析这一常见误区,指出获取节点文本内容需明确使用text()函数,而提取属性值则直接使用@attributeName。通过详细的代码示例,本文将指导您正确地从复杂的XML结构中…

    2025年12月14日
    000
  • PySpark中XPath函数提取XML元素文本内容为Null的解决方案

    在PySpark中使用xpath函数从XML字符串中提取元素内容时,常见问题是返回空值数组。这是因为默认的XPath表达式仅定位到元素节点而非其内部文本。正确的解决方案是在XPath表达式末尾添加/text(),明确指示提取元素的文本内容,从而确保数据被准确解析并避免空值。 1. PySpark中X…

    2025年12月14日
    000
  • PyTorch中高效查找张量B元素在张量A中的所有索引位置

    本教程旨在解决PyTorch中查找张量B元素在张量A中所有出现索引的挑战,尤其是在面对大规模张量时,传统广播操作可能导致内存溢出。文章提供了两种优化策略:一种是结合部分广播与Python循环的混合方案,另一种是纯Python循环迭代张量B的方案,旨在平衡内存效率与计算性能,并详细阐述了它们的实现方式…

    2025年12月14日
    000
  • PySpark中XPath函数提取XML节点文本内容指南:避免空值数组

    在使用PySpark的xpath函数从XML字符串中提取节点文本内容时,开发者常遇到返回空值数组的问题。本文将深入探讨这一常见误区,解释为何直接指定节点路径无法获取其文本,并提供正确的解决方案:通过在XPath表达式末尾添加/text()来精准定位并提取节点的字符串内容,确保数据能够被正确解析和利用…

    2025年12月14日
    000
  • Python super() 关键字详解:掌握继承中的方法调用机制

    本文深入探讨Python中super()关键字的用法,重点解析其在继承和方法重写场景下的行为。通过示例代码,阐明了super()如何允许子类调用父类(或更上层)的方法,尤其是在初始化方法__init__和普通方法中的执行顺序,帮助开发者清晰理解方法解析顺序(MRO)的工作机制。 什么是 super(…

    2025年12月14日
    000
  • PySpark中XPath提取XML数据指南:解决文本节点为空的问题

    本文旨在解决PySpark中使用xpath函数从XML字符串提取文本内容时,出现空值数组的问题。核心在于,当需要提取XML元素的文本内容时,必须在XPath表达式末尾明确使用/text()指令,而提取属性值则直接使用@attributeName。文章将通过具体示例代码,详细演示如何在PySpark中…

    2025年12月14日
    000
  • Python中将SQLAlchemy模型高效序列化为JSON的多种方法

    本文探讨了在Python后端API开发中,如何将SQLAlchemy模型对象及其关联的继承字段和关系数据转换为JSON格式。针对传统方法无法处理复杂模型结构和关联数据的问题,文章详细介绍了使用SQLAlchemy-serializer、Pydantic和SQLModel这三种主流库的实现方式,并提供…

    2025年12月14日
    000
  • Python字典分层数据提取与广度优先搜索(BFS)应用实践

    本文详细介绍了如何利用Python中的广度优先搜索(BFS)算法,从嵌套字典结构中根据起始节点和目标节点,分层提取数据。通过两种实现方式,包括基础BFS和优化版,演示了如何高效地遍历类似图的数据结构,并按迭代层级组织输出结果,同时处理循环和避免重复访问,为处理复杂数据依赖关系提供了专业解决方案。 1…

    2025年12月14日
    000
  • Python中super()关键字的深度解析与应用

    super()关键字在Python中扮演着至关重要的角色,它允许子类调用其父类(或根据方法解析顺序MRO链上的下一个类)的方法,即使子类已经重写了该方法。本文将详细探讨super()的工作原理、在继承体系中的行为,并通过示例代码演示其如何控制方法执行顺序,确保父类逻辑的正确调用,尤其是在处理方法覆盖…

    2025年12月14日
    000
  • 深入理解Python Enum的_missing_方法:实现灵活输入与固定值输出

    本文探讨了如何在Python enum中实现灵活的输入映射,同时保持枚举成员的固定值输出。通过利用 enum 类的 _missing_ 方法,我们可以自定义枚举成员的查找逻辑,将多种形式的输入(如字符串 ‘true’, ‘false’, ‘…

    2025年12月14日
    000
  • 解决Selenium无法点击Shadow DOM内元素:以Reddit登录为例

    Selenium在自动化测试中遇到Shadow DOM内的元素时,传统的XPath或CSS选择器会失效,导致NoSuchElementException。本文以Reddit登录按钮为例,详细讲解如何通过JavaScript路径定位并与Shadow DOM中的元素进行交互,从而有效解决Selenium…

    2025年12月14日
    000
  • PDF文档标题智能提取:从自定义机器学习到专业OCR解决方案

    本文探讨了从海量、多布局PDF文档中准确提取标题的挑战。面对不一致的元数据和多样化的页面结构,传统的规则或基于字体大小的提取方法往往失效。文章分析了基于PyMuPDF进行特征工程并训练分类器的设想,并最终推荐采用专业的OCR及文档处理系统,以其强大的模板定义、可视化配置和人工复核流程,实现更高效、鲁…

    2025年12月14日
    000
  • 解决Docker中Python模块导入错误的常见陷阱与排查指南

    本文旨在深入探讨在Docker容器中运行Python应用时,出现ModuleNotFoundError或ImportError的常见原因及排查方法。我们将通过一个具体案例,剖析即使PYTHONPATH和__init__.py配置正确,仍可能因构建上下文遗漏文件而导致导入失败的问题,并提供详细的解决方…

    2025年12月14日
    000
  • 在Python中合并Pandas Groupby聚合结果并生成组合条形图教程

    本教程详细介绍了如何将Pandas中两个基于相同分组键(如年、季节、天气情况)的聚合结果(例如总和与平均值)合并,并使用Matplotlib将它们绘制成一个清晰的组合条形图。文章通过数据合并、子图创建和精细化绘图步骤,指导用户实现高效的数据可视化,避免了直接绘制的常见问题。 在数据分析和可视化过程中…

    2025年12月14日
    000
  • Python Enum _missing_ 方法:实现灵活的成员查找与多值映射

    本文深入探讨Python enum.Enum 的 _missing_ 类方法,演示如何通过自定义查找逻辑,使枚举成员能够响应多种形式的输入(如”true”、”yes”、”T”),同时保持其内部值的独立性。这为处理外部不一致数据源…

    2025年12月14日
    000
  • 深入解析NumPy与Pickle的数据存储差异及优化策略

    本文深入探讨了NumPy数组与Python列表在使用np.save和pickle.dump进行持久化时,文件大小差异的根本原因。核心在于np.save以原始、未压缩格式存储数据,而pickle在特定场景下能通过对象引用优化存储,导致其文件看似更小。教程将详细解释这两种机制,并提供使用numpy.sa…

    2025年12月14日
    000

发表回复

登录后才能评论
关注微信