二叉树是最常见的数据结构之一,很多复杂问题(二叉搜索树、堆、Trie)都是在二叉树基础上扩展的。本文总结二叉树的核心操作和常见题型。
二叉树的遍历
遍历是二叉树最基本的操作,分为深度优先(DFS)和广度优先(BFS)两大类。
前序遍历(Preorder):根 → 左 → 右
1 2 3 4
| function preorder(root) { if (!root) return []; return [root.val, ...preorder(root.left), ...preorder(root.right)]; }
|
中序遍历(Inorder):左 → 根 → 右
1 2 3 4
| function inorder(root) { if (!root) return []; return [...inorder(root.left), root.val, ...inorder(root.right)]; }
|
中序遍历二叉搜索树(BST)会得到一个有序序列,这是一个很重要的性质。
后序遍历(Postorder):左 → 右 → 根
1 2 3 4
| function postorder(root) { if (!root) return []; return [...postorder(root.left), ...postorder(root.right), root.val]; }
|
层序遍历(Level Order):逐层从左到右
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function levelOrder(root) { if (!root) return []; const result = []; const queue = [root]; while (queue.length) { const len = queue.length; const level = []; for (let i = 0; i < len; i++) { const node = queue.shift(); level.push(node.val); if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } result.push(level); } return result; }
|
DFS(Depth-First Search)
DFS 指的是「一路走到头再回头」的搜索方式,二叉树的前序、中序、后序遍历都是 DFS 的具体形态。区别在于访问根节点的时机。
1 2 3 4 5 6 7 8 9
| function dfs(node) { if (!node) return; dfs(node.left); dfs(node.right); }
|
DFS 也可以写成非递归(用栈模拟),核心思想是手动维护一个栈来模拟系统调用栈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
function preorderIterative(root) { if (!root) return []; const stack = [root]; const result = []; while (stack.length) { const node = stack.pop(); result.push(node.val); if (node.right) stack.push(node.right); if (node.left) stack.push(node.left); } return result; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
function inorderIterative(root) { const stack = []; const result = []; let node = root; while (stack.length || node) { while (node) { stack.push(node); node = node.left; } node = stack.pop(); result.push(node.val); node = node.right; } return result; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
function postorderIterative(root) { if (!root) return []; const stack = [root]; const result = []; while (stack.length) { const node = stack.pop(); result.push(node.val); if (node.left) stack.push(node.left); if (node.right) stack.push(node.right); } return result.reverse(); }
|
三种非递归遍历总结:
| 遍历方式 |
栈用法 |
核心思路 |
| 前序 |
根压栈 → 弹出访问 → 右左压栈 |
右先入栈,左后出 |
| 中序 |
一路左压栈 → 弹出 → 转向右 |
用 while 走到底再回头 |
| 后序 |
根压栈 → 弹出记录 → 左右压栈 → 反转 |
前序变体 + reverse |
BFS(Breadth-First Search)
BFS 是「一层一层扫」的搜索方式,对应二叉树的层序遍历。BFS 通常用队列实现。
BFS 有两个常见变体:
- 逐层输出:上面
levelOrder 的例子,每一层一个数组
- 求最短路径:BFS 首次到达目标节点时走过的路径一定最短
1 2 3 4 5 6 7 8 9 10 11
| function minDepth(root) { if (!root) return 0; const queue = [[root, 1]]; while (queue.length) { const [node, depth] = queue.shift(); if (!node.left && !node.right) return depth; if (node.left) queue.push([node.left, depth + 1]); if (node.right) queue.push([node.right, depth + 1]); } }
|
序列化与反序列化
序列化是把二叉树转换成字符串(或数组),反序列化是把字符串还原回二叉树。主要用于数据传输和缓存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function serialize(root) { if (!root) return '#'; return root.val + ',' + serialize(root.left) + ',' + serialize(root.right); }
function deserialize(str) { const list = str.split(','); function build() { const val = list.shift(); if (val === '#') return null; const node = new TreeNode(Number(val)); node.left = build(); node.right = build(); return node; } return build(); }
|
也可以用层序遍历做序列化,结果更直观:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function serializeLevel(root) { if (!root) return '[]'; const queue = [root]; const result = []; while (queue.length) { const node = queue.shift(); if (node) { result.push(node.val); queue.push(node.left); queue.push(node.right); } else { result.push('#'); } } while (result[result.length - 1] === '#') result.pop(); return JSON.stringify(result); }
|
二叉树与链表的关系
二叉树和链表有很深的联系:
- 单链表可以看作只有右子树的二叉树——每个节点只有一个 next 指针,相当于二叉树节点只有 right child
- 二叉搜索树中序遍历得到有序链表——这常被用来「展平」二叉搜索树
- 链表 + 随机指针就是二叉树——每个节点有两个额外指针(left/right),就是二叉树结构
1 2 3 4 5 6 7 8 9 10 11 12 13
| function flatten(root) { if (!root) return; flatten(root.left); flatten(root.right); const right = root.right; root.right = root.left; root.left = null; let node = root; while (node.right) node = node.right; node.right = right; }
|
这种联系在题目中经常出现,本质上都是把树结构转换成线性结构来方便处理。