什么是二叉搜索树(Binary Search Tree)-BST
二叉搜索树是一种特殊的二叉树,有许多性质
- 每个节点都有一个键(和一个相关联的值),并且每个节点的键都是唯一的。
- 对于树中的每个节点
X
,其左子树中的所有键都小于X
的键,其右子树中的所有键都大于X
的键。 - 每个左右子树也都是二叉搜索树。
这些性质使得二叉搜索树在进行查找、插入和删除等操作时能够提供良好的性能,平均情况下可以在O(log n)
时间内完成这些操作(这里n
是树中节点的数量)。
二叉搜索树的遍历方式
二叉搜索树和普通二叉树的遍历方式一致,有DFS和BFS两种,二者的实现方式也有多重。
对于普通二叉树来说:深度优先(DFS)主要分为前中后序遍历,对于广度优先(BFS)则通常借用队列实现。对于一般二叉树,递归过程中还有回溯的过程,例如走一个左方向的分支走到头了,那么要调头,在走右分支。
对于二叉搜索树,不需要回溯的过程,因为节点的有序性就帮我们确定了搜索的方向。
例如要搜索元素为3的节点,我们不需要搜索其他节点,也不需要做回溯,查找的路径已经规划好了。
对于二叉搜索树来说:
- 中序遍历 特别有用,因为它按照排序顺序输出节点。
- 先序遍历 和 后序遍历 对于搜索操作没有特别的优势。
-
中序遍历(In-order Traversal):
- 访问左子树
- 访问根节点
- 访问右子树 这种遍历方式的特点是可以按照键的顺序访问树中的所有节点。
// 中序遍历(In-order)
public void inorder(TreeNode root, List<Integer> list) {
if (root == null) return;
inorder(root.left, list);
list.add(root.val);
inorder(root.right, list);
}
-
前序遍历(Pre-order Traversal):
- 访问根节点
- 访问左子树
- 访问右子树 前序遍历在复制树结构或者输出树的结构时特别有用。
// 前序遍历(Pre-order)
public void preorder(TreeNode root, List<Integer> list) {
if (root == null) return;
list.add(root.val);
preorder(root.left, list);
preorder(root.right, list);
}
-
后序遍历(Post-order Traversal):
- 访问左子树
- 访问右子树
- 访问根节点 后序遍历在删除树的节点或者计算空间占用时非常有用。
// 后序遍历(Post-order)
public void postorder(TreeNode root, List<Integer> list) {
if (root == null) return;
postorder(root.left, list);
postorder(root.right, list);
list.add(root.val);
}
当我们只是简单地遍历整个树时(不是搜索特定值),并不存在一种特殊的迭代方式来利用BST的性质,因为我们不得不访问树中的所有节点。但如果我们进行中序遍历,输出的节点序列将是有序的,这是利用BST性质的一个简单例子。
- 例如下题目,利用了二叉搜索树的优势
leetcode700.二叉搜索树中的搜索
public TreeNode searchBST(TreeNode root, int val) {
while (root != null && root.val != val) {
root = val < root.val ? root.left : root.right;
}
return root;
}
层次遍历(Level-order Traversal):
- 从根节点开始,一层层从上到下,同一层从左到右遍历所有的节点。 层次遍历通常需要使用队列来辅助实现。
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> currentLevel = new ArrayList<>();
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
currentLevel.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
result.add(currentLevel);
}
return result;
}
处理逻辑
处理逻辑的位置:
- 递归终止条件:通常是处理边界条件和递归基的地方,如检查一个节点是否为空,避免进一步的递归调用。
- DFS中处理逻辑在中节点处:对于DFS,处理逻辑可以放在进入左子树之前、之后,或者进入右子树之后,这取决于你需要的是先序、中序还是后序遍历的效果。
- 在BFS中处理逻辑:通常是在节点出队列时处理。
如果要在遍历过程中利用BST的性质,通常是与某个特定目标有关的问题,比如:
- 在给定值范围内搜索所有节点。
- 查找第k小或第k大的元素。