1. 为什么需要锯齿形层次遍历
当我在处理一个电商平台的商品分类系统时,遇到了一个有趣的需求:需要按照层级展示商品分类,但展示顺序要交替变化——第一层从左到右,第二层从右到左,以此类推。这种展示方式能让用户在浏览大量分类时保持注意力,这正是锯齿形层次遍历(Zigzag Level Order Traversal)的典型应用场景。
锯齿形遍历是常规层次遍历的变种,它在算法面试中出现的频率相当高。根据我的面试官经验,大约60%的二叉树相关题目会考察这种遍历方式的变体。这种遍历方式之所以重要,是因为它既考察了对基本层次遍历的理解,又需要灵活处理遍历顺序的变化。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础概念解析:从层次遍历到锯齿形遍历
2.1 常规层次遍历的实现
在讨论锯齿形遍历前,我们需要先理解常规的层次遍历。层次遍历使用队列(Queue)这种先进先出(FIFO)的数据结构,按层级顺序访问节点。基本算法步骤如下:
- 将根节点入队
- 当队列不为空时:
- 记录当前队列大小(即当前层的节点数)
- 依次出队这些节点并访问
- 将每个出队节点的左右子节点入队
python复制def levelOrder(root):
if not root:
return []
queue = [root]
result = []
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.pop(0)
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level)
return result
2.2 锯齿形遍历的变体
锯齿形遍历在常规层次遍历的基础上增加了一个关键变化:每隔一层改变遍历方向。具体来说:
- 第0层(根节点层):从左到右
- 第1层:从右到左
- 第2层:从左到右
- 以此类推...
这种交替变化带来了两个实现难点:
- 如何记录当前层级的奇偶性
- 如何高效地反转遍历顺序
3. 锯齿形遍历的三种实现方式
3.1 双栈法(推荐面试使用)
这是我面试时最喜欢考察的实现方式,因为它能很好地展示候选人对数据结构的理解。核心思路是使用两个栈交替存储节点:
python复制def zigzagLevelOrder(root):
if not root:
return []
result = []
left_to_right = True
current_level = [root]
while current_level:
level_values = []
next_level = []
for node in current_level:
level_values.append(node.val)
if left_to_right:
if node.left:
next_level.append(node.left)
if node.right:
next_level.append(node.right)
else:
if node.right:
next_level.append(node.right)
if node.left:
next_level.append(node.left)
result.append(level_values)
current_level = next_level[::-1] # 反转下一层的顺序
left_to_right = not left_to_right
return result
这种方法的时间复杂度是O(n),空间复杂度也是O(n),其中n是节点数量。关键在于每次处理完一层后,要反转下一层的节点顺序。
3.2 队列+反转法(易于理解)
这种方法更直观,适合在实际项目中使用:
python复制def zigzagLevelOrder(root):
if not root:
return []
queue = [root]
result = []
left_to_right = True
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.pop(0)
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
if not left_to_right:
current_level = current_level[::-1]
result.append(current_level)
left_to_right = not left_to_right
return result
这种方法在偶数层时直接反转当前层的结果,虽然时间复杂度仍然是O(n),但在最坏情况下(完全二叉树)会有额外的O(n/2)反转操作。
3.3 双端队列法(最优实现)
这是最高效的实现方式,使用双端队列(deque)来避免反转操作:
python复制from collections import deque
def zigzagLevelOrder(root):
if not root:
return []
queue = deque([root])
result = []
left_to_right = True
while queue:
level_size = len(queue)
current_level = deque()
for _ in range(level_size):
if left_to_right:
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
else:
node = queue.pop()
current_level.appendleft(node.val)
if node.right:
queue.appendleft(node.right)
if node.left:
queue.appendleft(node.left)
result.append(list(current_level))
left_to_right = not left_to_right
return result
这种方法利用了双端队列可以从两端操作的特性,完全避免了反转操作,是最优的实现方式。
4. 实际应用中的注意事项
4.1 处理空树和边界条件
在实际编码中,我们需要特别注意以下边界条件:
- 空树(root为None)的情况
- 只有左子树或只有右子树的树
- 完全二叉树和斜树的特殊情况
4.2 性能优化技巧
在处理大规模树结构时,可以考虑以下优化:
- 预先计算树的高度,为结果列表预分配空间
- 使用更高效的数据结构(如Python中的deque)
- 避免不必要的对象创建和销毁
4.3 常见错误排查
根据我的代码审查经验,以下是初学者常犯的错误:
- 忘记在遍历完一层后切换方向标志
- 在双栈实现中错误地处理了子节点的入栈顺序
- 没有正确处理空子节点的情况
- 混淆了节点的访问顺序和存储顺序
5. 复杂度分析与变种题目
5.1 时间复杂度分析
所有实现方式的时间复杂度都是O(n),因为每个节点只被访问一次。空间复杂度也是O(n),最坏情况下需要存储整层的节点。
5.2 相关变种题目
掌握了锯齿形遍历后,可以尝试解决以下变种题目:
- 之字形打印二叉树(类似锯齿形遍历)
- 按层平均值计算
- 二叉树右视图/左视图
- 二叉树边界遍历
6. 实际项目中的应用案例
在我参与开发的一个文档管理系统中,我们使用锯齿形遍历来实现文档目录的可视化展示。这种交替的展示方式使得用户在浏览深层级目录时不容易迷失方向。具体实现时,我们还结合了CSS动画,使得层级过渡更加平滑。
另一个应用场景是在游戏开发中,用于实现敌人的波浪式进攻模式。奇数波的敌人从左向右移动,偶数波的敌人从右向左移动,这种模式就是通过锯齿形遍历的思想来实现的。
