聊聊 “摸鱼式” 遍历 —— 受控遍历的小心机

"受控遍历"(Controlled Traversal)是针对数据结构遍历的一种按需触发、分步执行的遍历模式,核心特点是 "不一次性完成整个遍历,而是根据外部调用(如 next() 方法)逐步返回下一个元素",完全由外部控制遍历的节奏和进度。

它与 "一次性遍历"(如递归遍历、迭代遍历后直接输出所有结果)形成鲜明对比 ------ 后者会主动完成整个遍历流程并返回全部结果,而受控遍历则像 "拉取数据":外部要一个,遍历就执行到下一个元素并返回,不主动多走一步。

为什么需要受控遍历?

在很多场景下,我们不需要一次性获取所有遍历结果,而是按需使用(比如遍历大型数据集、实现迭代器等):

  • 若二叉搜索树有 100 万个节点,一次性遍历并存储所有结果会占用大量内存;
  • 实现迭代器(如 BST 迭代器)时,用户需要通过 next() 逐个获取元素,而非直接拿到全部。

此时受控遍历能做到 "空间优化" (无需存储所有结果)和"节奏可控"(按调用者需求推进),是高效处理这类场景的关键思路。

结合 BST 迭代器理解 "受控遍历" 的核心逻辑

之前实现的 BST 迭代器,就是 "受控遍历" 在二叉搜索树(中序遍历)上的典型应用。我们通过 "栈 + 分步触发" 实现受控,具体拆解每一步的 "控制逻辑":

1. 初始化:只做 "遍历准备",不启动完整遍历

初始化时(init 方法),我们并非直接遍历整棵树,而是仅执行 "遍历的前置操作"------ 把根节点及所有左子节点压入栈(通过 _push_left 辅助函数):

ruby 复制代码
def __init__(self, root: TreeNode):
    self.stack = []
    self._push_left(root)  # 仅压入左链,停在"第一个要返回的元素"(栈顶)
def _push_left(self, node: TreeNode):
    while node:
        self.stack.append(node)
        node = node.left  # 沿左链向下,直到最左节点(BST中序的第一个元素)
  • 这一步的 "控制":遍历仅推进到 "第一个待返回元素"(栈顶),之后就暂停,等待外部调用。
  • 例:BST 7→3,15→9,20,初始化后栈中是 [7,3],栈顶是 3(中序第一个元素),遍历暂存于此。

2. 外部调用 next():触发 "一步遍历",返回当前元素后暂停

当外部调用 next() 时,才触发 "遍历的下一步",且仅执行到 "获取当前元素 + 为下一次调用做准备",之后再次暂停:

python 复制代码
def next(self) -> int:
    # 1. 取出当前要返回的元素(栈顶,即上一步暂停的位置)
    node = self.stack.pop()  # 第一次调用时弹出 3
    # 2. 为下一次调用做准备:若当前节点有右子树,压入右子树的所有左节点
    if node.right:
        self._push_left(node.right)  # 3 无右子树,不操作;下次弹出7时,压入15、9
    return node.val  # 返回当前元素,遍历暂停
  • 这一步的 "控制":每调用一次 next(),仅完成 "返回一个元素 + 准备下一个元素",不继续推进。
  • 例:
    • 第一次调用 next():弹出 3(返回),3 无右子树,栈变为 [7],遍历暂停;
    • 第二次调用 next():弹出 7(返回),7 有右子树 15,调用 _push_left(15) 压入 15、9,栈变为 [15,9],遍历暂停;
    • 第三次调用 next():弹出 9(返回),9 无右子树,栈变为 [15],遍历暂停。

3. hasNext():判断 "遍历是否可继续"

通过栈是否为空,判断是否还有未遍历的元素,给外部调用者提供 "是否需要继续调用 next()" 的依据:

python 复制代码
def hasNext(self) -> bool:
    return len(self.stack) > 0  # 栈非空 → 还有下一个元素
  • 这一步是 "控制的辅助":让调用者知道遍历进度,避免无效调用。

受控遍历的关键特征

总结来说,"受控遍历" 必须满足以下 3 个核心特征,才能区别于普通遍历:

  1. 分步执行:遍历过程被拆分为多个独立步骤,每一步只处理 "获取一个元素";
  1. 外部触发:每一步的执行必须由外部调用(如 next())触发,不主动推进;
  1. 状态保存:遍历暂停时,必须保存当前进度(如 BST 迭代器中栈的状态),确保下次调用能从暂停处继续,不重复、不遗漏。

其他场景中的受控遍历

除了 BST 迭代器,"受控遍历" 在很多场景中都有应用,本质思路一致:

  • 链表迭代器:通过 current 指针保存当前位置,next() 时 current = current.next,分步返回链表元素;
  • 文件读取:按行读取大文件时,readline() 方法就是受控遍历 ------ 调用一次读一行,不一次性加载所有行;
  • 迭代器模式:所有编程语言的迭代器(如 Python 的 iter()/next()),底层都是 "受控遍历" 的实现。

通过 "受控遍历",我们能在遍历大型数据结构时显著优化内存占用,同时让遍历节奏完全适配外部需求,是一种兼顾效率和灵活性的遍历思想。

相关推荐
YLCHUP14 分钟前
【联通分量】题解:P13823 「Diligent-OI R2 C」所谓伊人_连通分量_最短路_01bfs_图论_C++算法竞赛
c语言·数据结构·c++·算法·图论·广度优先·图搜索算法
花火|1 小时前
算法训练营day62 图论⑪ Floyd 算法精讲、A star算法、最短路算法总结篇
算法·图论
GuGu20241 小时前
新手刷题对内存结构与形象理解的冲突困惑
算法
汤永红1 小时前
week4-[二维数组]平面上的点
c++·算法·平面·信睡奥赛
晴空闲雲1 小时前
数据结构与算法-字符串、数组和广义表(String Array List)
数据结构·算法
袁煦丞2 小时前
SimpleMindMap私有部署团队脑力风暴:cpolar内网穿透实验室第401个成功挑战
前端·程序员·远程工作
Dovis(誓平步青云)2 小时前
《C++哈希表:高效数据存储与检索的核心技术》
数据结构·散列表·哈希表
颜如玉3 小时前
位运算技巧总结
后端·算法·性能优化
冷月半明3 小时前
时间序列篇:Prophet负责优雅,LightGBM负责杀疯
python·算法