二叉树基础与遍历

本节目标

节点与四种遍历:

// 运行环境:Node.js 14+
// 保存为 dsa-l11.js,执行:node dsa-l11.js

class TreeNode {
  constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right }
}

// 前序(根→左→右)、中序(左→根→右)、后序(左→右→根)
function preorder(root, out = []) { if (!root) return out; out.push(root.val); preorder(root.left, out); preorder(root.right, out); return out }
function inorder(root, out = [])  { if (!root) return out; inorder(root.left, out); out.push(root.val); inorder(root.right, out); return out }
function postorder(root, out = []) { if (!root) return out; postorder(root.left, out); postorder(root.right, out); out.push(root.val); return out }

// 层序遍历(BFS,用队列)
function levelOrder(root) {
  if (!root) return []
  const q = [root], res = []
  while (q.length) {
    const node = q.shift() // 小数据量无妨;生产用头指针队列
    res.push(node.val)
    if (node.left) q.push(node.left)
    if (node.right) q.push(node.right)
  }
  return res
}

// 构建示例树:    1
//               / \
//              2   3
const root = new TreeNode(1, new TreeNode(2), new TreeNode(3))
console.log('pre ', preorder(root))  // [1, 2, 3]
console.log('in  ', inorder(root))  // [2, 1, 3]
console.log('post', postorder(root)) // [2, 3, 1]
console.log('level', levelOrder(root)) // [1, 2, 3]

迭代版前序(用栈模拟递归):

function preorderIter(root) {
  const res = [], st = []
  if (root) st.push(root)
  while (st.length) {
    const n = st.pop()
    res.push(n.val)
    if (n.right) st.push(n.right) // 右先入后出,保证左先处理
    if (n.left) st.push(n.left)
  }
  return res
}

名词解释

课后练习

  1. 为什么 BST 的中序遍历是升序的?
    • 答案:BST 定义"左子树所有值 < 根 < 右子树所有值",中序是"左→根→右",恰好从小到大访问,故升序。
  2. 迭代前序为什么"右孩子先入栈"?
    • 答案:栈是后进先出,要让左孩子先被弹出处理,就得把右孩子先压栈、左孩子后压栈,弹出时左先出。

总结

二叉树是递归思想最完美的练习场——因为树本身就是"根 + 左子树 + 右子树"的递归定义,所以 90% 的树题都能写成"处理根 + 递归左右"的三行代码。前中后序不是死记硬背的顺序,而是"根在什么时候被访问":根最先是前置、中间是中置、最后是后置,这个视角能让你在写任何树递归时不迷路。层序遍历则切换到 BFS 思维,用队列一层层展开,适合"求最小深度""之字形打印"等问题。我想强调一个工程感悟:递归写起来爽,但太深的树会爆调用栈,此时要学会用显式栈做迭代版(如上面的 preorderIter)。把四种遍历练到闭眼能写,你就拿到了打开整棵"树与堆"章节的钥匙。