"受控遍历"(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 个核心特征,才能区别于普通遍历:
- 分步执行:遍历过程被拆分为多个独立步骤,每一步只处理 "获取一个元素";
- 外部触发:每一步的执行必须由外部调用(如 next())触发,不主动推进;
- 状态保存:遍历暂停时,必须保存当前进度(如 BST 迭代器中栈的状态),确保下次调用能从暂停处继续,不重复、不遗漏。
其他场景中的受控遍历
除了 BST 迭代器,"受控遍历" 在很多场景中都有应用,本质思路一致:
- 链表迭代器:通过 current 指针保存当前位置,next() 时 current = current.next,分步返回链表元素;
- 文件读取:按行读取大文件时,readline() 方法就是受控遍历 ------ 调用一次读一行,不一次性加载所有行;
- 迭代器模式:所有编程语言的迭代器(如 Python 的 iter()/next()),底层都是 "受控遍历" 的实现。
通过 "受控遍历",我们能在遍历大型数据结构时显著优化内存占用,同时让遍历节奏完全适配外部需求,是一种兼顾效率和灵活性的遍历思想。