小木的算法日记-多叉树的递归/层序遍历
🌲 从二叉树到森林:一文彻底搞懂多叉树遍历的艺术
🚀 引言
你好,未来的算法大神!
在数据结构的世界里,“树”无疑是最核心、最迷人的概念之一。我们中的大多数人都是从 二叉树 开始入门的,它简洁、优雅,是许多复杂算法的基石。但现实世界的问题往往更加复杂:一个公司的组织架构、一个操作系统的文件目录……这些都不是简单的“一生二”就能描述的。
这时,多叉树(N-ary Tree) 和 森林(Forest) 便登上了舞台。
这篇文章将带你完成一次平滑的认知升级:
-
理解:从二叉树到多叉树,再到森林,它们之间究竟是什么关系?
-
掌握:如何将二叉树的遍历思想(DFS 和 BFS)优雅地迁移到多叉树上?
-
精通:学习层序遍历的三种核心写法,从入门到精通,并理解其背后的设计哲学。
准备好了吗?让我们一起开始这场探索树木深处奥秘的旅程吧!
🧐 概念解析:从二叉到多叉,再到森林
在深入遍历之前,我们先花一分钟理清这几个概念的关系。
核心思想:多叉树是二叉树概念的自然延伸,而森林则是多叉树的集合。
1. 二叉树 (Binary Tree)
每个节点最多只有两个子节点,通常称为 left(左子节点)和 right(右子节点)。
# 二叉树的节点定义
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = right
2. 多叉树 (N-ary Tree)
每个节点可以拥有任意数量的子节点。我们通常用一个列表 children 来存储所有子节点。
# 多叉树的节点定义
class Node:def __init__(self, val=0, children=None):self.val = val# 如果不提供 children,则默认为一个空列表self.children = children if children is not None else []
3. 森林 (Forest)
顾名思义,森林就是多棵树的集合。从结构上看,一片森林可以看作是一个包含了多个多叉树根节点的列表。有趣的是,一棵多叉树本身,也可以被视作一个只有一个成员的特殊森林。
🧭 深度优先遍历 (DFS):递归的思想精髓
DFS 的核心是“一路走到底,再回头”。递归是实现 DFS 最自然的方式。让我们用一个绝妙的比喻来理解它——探索房间与钥匙。
二叉树的探索之旅
想象你进入了一座由房间构成的迷宫,每个房间最多有两扇门,分别由“左钥匙”和“右钥匙”打开。
# 二叉树的遍历框架
def traverse_binary_tree(root: TreeNode):if root is None:return # 门后是空的,返回# azioni 前序位置: 刚进入房间,还没开新门print(f"前序:到达房间 {root.val}")traverse_binary_tree(root.left) # 用左钥匙,探索左边的分支# azioni 中序位置: 从左边分支回来,准备去右边print(f"中序:探索完 {root.val} 的左侧")traverse_binary_tree(root.right) # 用右钥匙,探索右边的分支# azioni 后序位置: 左右都探索完毕,准备离开这个房间print(f"后序:离开房间 {root.val}")
-
前序位置:刚进入房间,立即执行的操作。
-
中序位置:探索完左边所有房间,准备去右边之前执行的操作。
-
后序位置:左右两边的房间都探索完毕,离开当前房间前执行的操作。
多叉树的探索升级
现在,迷宫升级了!每个房间里不再是固定的两把钥匙,而是一把钥匙串 children,上面挂着通往所有下一级房间的钥匙。
# N 叉树的遍历框架
def traverse_n_ary_tree(root: Node):if root is None:return # 门后是空的,返回# azioni 前序位置: 刚进入房间,还没用任何一把钥匙print(f"前序:到达房间 {root.val}")for child in root.children: # 依次使用钥匙串上的每一把钥匙traverse_n_ary_tree(child)# azioni 后序位置: 所有子房间都探索完毕,准备离开print(f"后序:离开房间 {root.val}")
核心区别:
-
二叉树是固定的 left 和 right,所以有中序位置。
-
多叉树是通过 for 循环遍历所有子节点,因此天然地只有前序和后序位置。前序在循环前,后序在循环后。
🗺️ 层序遍历 (BFS):逐层扫描的广度智慧
BFS 像是在水面投下一颗石子,涟漪从中心一圈一圈地向外扩散。它不深入,而是先扫遍同一层的所有节点。实现 BFS 的利器是队列(Queue)。
下面,我们将通过三种方案,层层递进,彻底掌握多叉树的层序遍历。
方案一:基础版 - 只求遍历,不问出处
这是最纯粹的层序遍历,目标仅仅是按层次顺序访问到每个节点,不关心它在哪一层。
from collections import dequedef level_order_simple(root: Node):if root is None:returnq = deque([root]) # 初始化队列,放入根节点while q:cur = q.popleft() # 从队列头部取出一个节点# 访问当前节点print(f"访问到节点: {cur.val}")# 将其所有子节点加入队列尾部for child in cur.children:q.append(child)
优点:代码简洁,逻辑清晰。
缺点:无法知道当前节点所在的深度。
方案二:进阶版 - 巧用 size 记录层数
如果我们想在遍历时知道每个节点位于第几层,怎么办?答案是:在每一轮循环中,只处理当前层的所有节点。
from collections import dequedef level_order_with_depth_v1(root: Node):if root is None:returnq = deque([root])depth = 1 # 根节点视为第 1 层while q:# 关键:在循环开始前,记录当前队列大小,即当前层的节点数level_size = len(q)print(f"--- 开始处理第 {depth} 层,该层有 {level_size} 个节点 ---")for i in range(level_size):cur = q.popleft()print(f" depth = {depth}, val = {cur.val}")for child in cur.children:q.append(child)# 当前层处理完毕,深度加一depth += 1
图解示例:
假设我们有如下三层树:
1 (根)/ | \2 3 4/|\5 6 7
遍历过程:
-
初始: q = [1], depth = 1。
-
第一次 while: level_size = 1。
-
处理节点 1,将其子节点 [2, 3, 4] 入队。
-
q 变为 [2, 3, 4]。
-
depth 变为 2。
-
-
第二次 while: level_size = 3。
-
处理节点 2,将其子节点 [5, 6, 7] 入队。
-
处理节点 3, 4 (无子节点)。
-
q 变为 [5, 6, 7]。
-
depth 变为 3。
-
-
第三次 while: level_size = 3。
-
处理节点 5, 6, 7。
-
q 变为空,循环结束。
-
这种写法的优势在于:
-
明确分层:for 循环天然地将每一层的节点处理逻辑框在一起。
-
批量处理:便于对每一层进行统计,如计算层和、找到层最大值等。
方案三:终极版 - State 对象,捆绑节点与深度
有时候,我们希望信息能够随着节点在队列中传递,而不是依赖外部变量。这时,我们可以引入一个 State 类。
比喻时间:快递员与行李牌
想象你是一个快递员,在一个树状小区里送货。
Node:是收件地址(房间)。
State:是贴在包裹上的“行李牌”,上面清晰地写着 收件地址(Node) 和 楼层号(depth)。
这样,无论包裹传到谁手里,信息都不会丢失。
from collections import deque# “行李牌”类,捆绑节点和它所在的深度
class State:def __init__(self, node: Node, depth: int):self.node = nodeself.depth = depthdef level_order_with_depth_v2(root: Node):if root is None:returnq = deque([State(root, 1)]) # 把贴好行李牌的根节点包裹放入队列while q:# 取出队列中的第一个包裹current_state = q.popleft()node = current_state.nodedepth = current_state.depth# 查看行李牌信息,并进行处理print(f"depth = {depth}, val = {node.val}")# 为所有子节点制作新的行李牌(深度+1),并放入队列for child in node.children:q.append(State(child, depth + 1))
为什么这种写法很重要?
虽然在这个场景下,方案二看起来更简洁,但 State 模式的思想* 当问题变得更复杂时,比如在著名的 Dijkstra 或 A 算法中,节点需要携带的就不仅仅是深度,可能还有“从起点到此的成本”等更多信息。State 类提供了一个完美的框架来封装这些状态。
✨ 总结
恭喜你,坚持看到了最后!现在,让我们快速回顾一下今天学到的核心知识:
-
从二叉到多叉:多叉树是二叉树的泛化,遍历逻辑从处理固定的 left/right 变成了 for 循环处理 children 列表。
-
DFS (深度优先):使用递归,代码优雅,有前序和后序两个关键操作位置,适合“一条路走到黑”的场景。
-
BFS (广度优先):使用队列,逐层扫描,适合寻找最短路径等场景。我们掌握了三种实现方式:
-
基础版:最简单,但无法获知深度。
-
size 进阶版:巧妙地按层处理,能记录深度,是面试高频写法。
-
State 终极版:将节点与状态(如深度)绑定,扩展性极强,是通向更高级算法的钥匙。
-
理论是舟,实践是海。现在,打开你的编辑器,用今天学到的知识去 LeetCode 上征服几道多叉树的题目吧!你会发现,世界豁然开朗。
人之道损不足而利有余,天之道损有余而利不足。
相关文章:
小木的算法日记-多叉树的递归/层序遍历
🌲 从二叉树到森林:一文彻底搞懂多叉树遍历的艺术 🚀 引言 你好,未来的算法大神! 在数据结构的世界里,“树”无疑是最核心、最迷人的概念之一。我们中的大多数人都是从 二叉树 开始入门的,它…...

消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...

ubuntu系统文件误删(/lib/x86_64-linux-gnu/libc.so.6)修复方案 [成功解决]
报错信息:libc.so.6: cannot open shared object file: No such file or directory: #ls, ln, sudo...命令都不能用 error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory重启后报错信息&…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
云原生周刊:k0s 成为 CNCF 沙箱项目
开源项目推荐 HAMi HAMi(原名 k8s‑vGPU‑scheduler)是一款 CNCF Sandbox 级别的开源 K8s 中间件,通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度,为容器提供统一接口,实现细粒度资源配额…...
0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化
是不是受够了安装了oracle database之后sqlplus的简陋,无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话,配置.bahs_profile后也能解决上下翻页这些,但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可,…...
6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础
第三周 Day 3 🎯 今日目标 理解类(class)和对象(object)的关系学会定义类的属性、方法和构造函数(init)掌握对象的创建与使用初识封装、继承和多态的基本概念(预告) &a…...
k8s从入门到放弃之HPA控制器
k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...
Python网页自动化Selenium中文文档
1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API,让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API,你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...
区块链技术概述
区块链技术是一种去中心化、分布式账本技术,通过密码学、共识机制和智能合约等核心组件,实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点:数据存储在网络中的多个节点(计算机),而非…...
comfyui 工作流中 图生视频 如何增加视频的长度到5秒
comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗? 在ComfyUI中实现图生视频并延长到5秒,需要结合多个扩展和技巧。以下是完整解决方案: 核心工作流配置(24fps下5秒120帧) #mermaid-svg-yP…...

数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...

保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!
目录 补间动画 1.创建资源文件夹 2.设置文件夹类型 3.创建.xml文件 4.样式设计 5.动画设置 6.动画的实现 内容拓展 7.在原基础上继续添加.xml文件 8.xml代码编写 (1)rotate_anim (2)scale_anim (3)translate_anim 9.MainActivity.java代码汇总 10.效果展示 逐帧…...

沙箱虚拟化技术虚拟机容器之间的关系详解
问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西,但是如果把三者放在一起,它们之间到底什么关系?又有什么联系呢?我不是很明白!!! 就比如说: 沙箱&#…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...

C++实现分布式网络通信框架RPC(2)——rpc发布端
有了上篇文章的项目的基本知识的了解,现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...
基于鸿蒙(HarmonyOS5)的打车小程序
1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...

VisualXML全新升级 | 新增数据库编辑功能
VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...

Sklearn 机器学习 缺失值处理 获取填充失值的统计值
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...

FFmpeg avformat_open_input函数分析
函数内部的总体流程如下: avformat_open_input 精简后的代码如下: int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...
如何配置一个sql server使得其它用户可以通过excel odbc获取数据
要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据,你需要完成以下配置步骤: ✅ 一、在 SQL Server 端配置(服务器设置) 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到:SQL Server 网络配…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
文/法律实务观察组 在债务重组领域,专业机构的核心价值不仅在于减轻债务数字,更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明,合法债务优化需同步实现三重平衡: 法律刚性(债…...

嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...

从物理机到云原生:全面解析计算虚拟化技术的演进与应用
前言:我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM(Java Virtual Machine)让"一次编写,到处运行"成为可能。这个软件层面的虚拟化让我着迷,但直到后来接触VMware和Doc…...

五子棋测试用例
一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏,有着深厚的文化底蕴。通过将五子棋制作成网页游戏,可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家,都可以通过网页五子棋感受到东方棋类…...

GraphQL 实战篇:Apollo Client 配置与缓存
GraphQL 实战篇:Apollo Client 配置与缓存 上一篇:GraphQL 入门篇:基础查询语法 依旧和上一篇的笔记一样,主实操,没啥过多的细节讲解,代码具体在: https://github.com/GoldenaArcher/graphql…...
微服务通信安全:深入解析mTLS的原理与实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言:微服务时代的通信安全挑战 随着云原生和微服务架构的普及,服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...

【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL
ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...