有序数组 vs 二叉搜索树(BST)
有序数组的优缺点
-
优点:
-
查找快(O(log n)):可以用二分查找快速定位数据。
-
-
缺点:
-
插入/删除慢(O(n)):每次插入或删除都需要移动后面的所有元素。
-
示例:
-
插入
17
→ 后面所有元素后移。 -
删除
17
→ 后面所有元素前移。
-
-
适用场景:适合静态数据(很少修改,频繁查找)。
二叉搜索树(BST)简介
-
定义:一种能高效支持查找、插入、删除(均摊 O(log n))的树形结构。
-
核心规则:
-
左子树的所有节点 < 根节点。
-
右子树的所有节点 > 根节点。
-
中序遍历结果是从小到大有序的。
-
-
比如下面这棵树就是一颗二叉搜索树
-
BST 的基本操作
(1) 插入(Insert)
- 若插入值小于当前节点值,递归插入到左子树。
- 若插入值大于当前节点值,递归插入到右子树。
-
public TreeNode insert(TreeNode root, int val) { if (root == null) { return new TreeNode(val); } if (val < root.val) { root.left = insert(root.left, val); } else if (val > root.val) { root.right = insert(root.right, val); } return root; }
(2) 查找(Search)
-
实际上就是二分查找的思想
-
即从根节点开始比较。
-
目标值 < 当前节点 → 往左子树找。
-
目标值 > 当前节点 → 往右子树找。
-
找到相等值 → 成功;遇到空节点 → 失败。
-
public TreeNode search(TreeNode root, int val) { if (root == null || root.val == val) { return root; } if (val < root.val) { return search(root.left, val); } else { return search(root.right, val); } }
(3) 删除(Delete)
public TreeNode delete(TreeNode root, int val) {
if (root == null) return null;
if (val < root.val) {
root.left = delete(root.left, val);
} else if (val > root.val) {
root.right = delete(root.right, val);
} else {
// Case 1 & 2: 无子节点或只有一个子节点
return root.left == null?root.right:root.left;
// Case 3: 有两个子节点
TreeNode minNode = findMin(root.right);
root.val = minNode.val;
root.right = delete(root.right, minNode.val);
}
return root;
}
private TreeNode findMin(TreeNode node) {
while (node.left != null) {
node = node.left;
}
return node;
}
分三种情况处理:
-
叶子节点:直接删除。
-
-
只有左或右子树(如 10 只有左子树):用它的唯一子树替代自己。
-
-
有左右子树(如根节点
9
):-
找到该节点的 前驱(左子树最大节点) 或 后继(右子树最小节点)。
-
用前驱或后继的值覆盖待删除节点。
-
删除原来的前驱/后继节点(它一定是叶子节点或单子节点,递归处理)。
-
这里演示一下用左子树中的最大节点覆盖待删除节点
-
-
为什么选这两个节点?
-
前驱:是待删除节点在中序遍历序列中的前一个节点,替换后不会破坏BST性质。
-
后继:是待删除节点在中序遍历序列中的后一个节点,同样保证结构正确。
-
-
BST 的极端情况与问题
-
问题:如果插入的数据本身有序(如
1, 2, 3, 4, 5
),BST 会退化成链表,查找效率降为 O(n)。-
示例:依次插入
1, 2, 3, 4, 5
→ 树变成一条向右的链。
-
-
解决方案:使用平衡二叉搜索树(如 AVL 树、红黑树),通过旋转操作保持树平衡。