二叉搜索树(BST)
最后更新
二叉搜索树让数值保持有序:对每个节点来说,左子树中的所有值都比它小,右子树中的所有值都比它大。要插入或查找一个值,就从根节点出发,根据比较结果不断向左或向右走,每一步都把搜索范围缩小一半。点击上方的“播放”,观察数值如何通过比较找到位置,以及一次查找如何沿着树向下走。
在平衡的树上,这些操作的时间复杂度是 O(log n)。问题在于:插入已经有序的数据会让树退化成一条链表,操作变成 O(n),这正是 AVL 树和红黑树这类自平衡变体存在的原因。
时间和空间复杂度
| 操作 | 平衡时 | 最坏情况(倾斜) |
|---|---|---|
| 查找 | O(log n) | O(n) |
| 插入 | O(log n) | O(n) |
| 删除 | O(log n) | O(n) |
| 空间 | O(n) | O(n) |
分步说明(插入)
| 步骤 | 发生了什么 |
|---|---|
| 1 | 如果树为空,新值就成为根节点。 |
| 2 | 否则从根节点开始。 |
| 3 | 如果新值更小,就走到左子节点;如果更大,就走到右子节点。 |
| 4 | 重复,直到遇到一个空位。 |
| 5 | 把新值作为叶子节点放在那里。 |
示例演算
把 [5, 3, 8, 1, 4] 逐个插入一棵空树:
| 插入 | 经过的路径 | 操作 |
|---|---|---|
5 | - | 树为空,所以 5 成为根节点。 |
3 | 5 | 3 < 5,向左走;该位置为空,把 3 作为 5 的左子节点。 |
8 | 5 | 8 > 5,向右走;该位置为空,把 8 作为 5 的右子节点。 |
1 | 5 -> 3 | 1 < 5 向左走,再 1 < 3 向左走;把 1 作为 3 的左子节点。 |
4 | 5 -> 3 | 4 < 5 向左走,再 4 > 3 向右走;把 4 作为 3 的右子节点。 |
什么时候使用二叉搜索树
| 适合使用的情况 | 不适合使用的情况 |
|---|---|
| 需要有序存储加快速查找,并且插入的数据顺序随机。 | 数据到达时已经有序,不平衡的 BST 每次操作会退化为 O(n)。 |
| 希望通过中序遍历直接按顺序得到所有值。 | 只需要判断元素是否存在,不需要顺序,哈希表的平均查找是 O(1)。 |
| 需要范围查询,或查找某个键的后继和前驱。 | 需要保证 O(log n) 的上界,这时应改用自平衡的 AVL 树或红黑树。 |
| 数据经常变化,用静态有序数组更新起来代价很高。 | 数据固定且只读,有序数组加二分查找更简单,对缓存也更友好。 |
Binary Search Tree 代码
简洁、可运行的 Binary Search Tree 实现,提供 Python, JavaScript, Java, C++, C 版本。选择一种语言,复制代码,或在 Coddy 在线编辑器中直接打开。
Binary Search Tree 的 Python 代码
1class Node:2 def __init__(self, key):3 self.key = key4 self.left = None5 self.right = None6
7
8def insert(node, key):9 if node is None:10 return Node(key)11 if key < node.key:12 node.left = insert(node.left, key)13 elif key > node.key:14 node.right = insert(node.right, key)15 return node # duplicates are ignored16
17
18def search(node, key):19 if node is None:20 return False21 if key == node.key:22 return True23 if key < node.key:24 return search(node.left, key)25 return search(node.right, key)26
27
28def inorder(node):29 if node is None:30 return []31 return inorder(node.left) + [node.key] + inorder(node.right)32
33
34root = None35for key in [8, 3, 10, 1, 6, 14, 4, 7]:36 root = insert(root, key)37
38print("Inorder (sorted):", inorder(root))39print("search(6): ", search(root, 6))40print("search(5): ", search(root, 5))Binary Search Tree 的 JavaScript 代码
1class Node {2 constructor(value) {3 this.value = value;4 this.left = null;5 this.right = null;6 }7}8
9class BinarySearchTree {10 constructor() {11 this.root = null;12 }13
14 insert(value) {15 const attach = (node) => {16 if (!node) return new Node(value);17 if (value < node.value) node.left = attach(node.left);18 else node.right = attach(node.right);19 return node;20 };21 this.root = attach(this.root);22 }23
24 search(value) {25 let current = this.root;26 while (current) {27 if (value === current.value) return true;28 current = value < current.value ? current.left : current.right;29 }30 return false;31 }32
33 // Inorder traversal of a BST visits values in sorted order34 inorder(node = this.root, out = []) {35 if (!node) return out;36 this.inorder(node.left, out);37 out.push(node.value);38 this.inorder(node.right, out);39 return out;40 }41}42
43const bst = new BinarySearchTree();44for (const value of [8, 3, 10, 1, 6, 14, 4]) bst.insert(value);45console.log("Inorder:", bst.inorder().join(" "));46console.log("search(6):", bst.search(6));47console.log("search(7):", bst.search(7));Binary Search Tree 的 Java 代码
1public class Main {2 static class Node {3 int key;4 Node left, right;5 Node(int key) { this.key = key; }6 }7
8 static Node insert(Node node, int key) {9 if (node == null) return new Node(key);10 // Smaller keys go left, larger keys go right11 if (key < node.key) node.left = insert(node.left, key);12 else if (key > node.key) node.right = insert(node.right, key);13 return node;14 }15
16 static boolean search(Node node, int key) {17 if (node == null) return false;18 if (key == node.key) return true;19 return key < node.key ? search(node.left, key) : search(node.right, key);20 }21
22 static void inorder(Node node, StringBuilder sb) {23 if (node == null) return;24 inorder(node.left, sb);25 sb.append(node.key).append(" ");26 inorder(node.right, sb);27 }28
29 public static void main(String[] args) {30 Node root = null;31 int[] keys = {8, 3, 10, 1, 6, 14, 4};32 for (int k : keys) root = insert(root, k);33
34 StringBuilder sb = new StringBuilder();35 inorder(root, sb);36 System.out.println("Inorder (sorted): " + sb.toString().trim());37 System.out.println("search 6: " + search(root, 6));38 System.out.println("search 7: " + search(root, 7));39 }40}Binary Search Tree 的 C++ 代码
1#include <iostream>2
3struct Node {4 int value;5 Node* left = nullptr;6 Node* right = nullptr;7 explicit Node(int v) : value(v) {}8};9
10Node* insert(Node* root, int value) {11 if (root == nullptr) return new Node(value);12 if (value < root->value) root->left = insert(root->left, value);13 else if (value > root->value) root->right = insert(root->right, value);14 return root; // duplicates are ignored15}16
17bool contains(const Node* root, int value) {18 // Walk down: smaller goes left, larger goes right19 while (root != nullptr) {20 if (value == root->value) return true;21 root = value < root->value ? root->left : root->right;22 }23 return false;24}25
26void inorder(const Node* node) {27 if (node == nullptr) return;28 inorder(node->left);29 std::cout << node->value << " ";30 inorder(node->right);31}32
33int main() {34 Node* root = nullptr;35 for (int value : {50, 30, 70, 20, 40, 60, 80}) {36 root = insert(root, value);37 }38 std::cout << "Sorted: ";39 inorder(root);40 std::cout << "\n" << std::boolalpha;41 std::cout << "contains(40): " << contains(root, 40) << "\n";42 std::cout << "contains(45): " << contains(root, 45) << "\n";43 return 0;44}Binary Search Tree 的 C 代码
1#include <stdbool.h>2#include <stdio.h>3#include <stdlib.h>4
5typedef struct Node {6 int value;7 struct Node* left;8 struct Node* right;9} Node;10
11Node* newNode(int value) {12 Node* n = malloc(sizeof(Node));13 n->value = value;14 n->left = n->right = NULL;15 return n;16}17
18Node* insert(Node* root, int value) {19 if (root == NULL) return newNode(value);20 if (value < root->value) root->left = insert(root->left, value);21 else if (value > root->value) root->right = insert(root->right, value);22 return root; // duplicates are ignored23}24
25bool contains(const Node* root, int value) {26 // Walk down: smaller goes left, larger goes right27 while (root != NULL) {28 if (value == root->value) return true;29 root = value < root->value ? root->left : root->right;30 }31 return false;32}33
34void inorder(const Node* node) {35 if (node == NULL) return;36 inorder(node->left);37 printf("%d ", node->value);38 inorder(node->right);39}40
41int main(void) {42 int values[] = {50, 30, 70, 20, 40, 60, 80};43 Node* root = NULL;44 for (int i = 0; i < 7; i++) root = insert(root, values[i]);45 printf("Sorted: ");46 inorder(root);47 printf("\n");48 printf("contains(40): %s\n", contains(root, 40) ? "true" : "false");49 printf("contains(45): %s\n", contains(root, 45) ? "true" : "false");50 return 0;51}二叉搜索树常见问题
二叉搜索树的时间复杂度是多少?
O(log n),因为每次比较都会排除剩下一半的节点。最坏情况下(有序插入让树倾斜成一条链),它们会退化为 O(n)。二叉树和二叉搜索树有什么区别?
二叉树是指每个节点最多有两个子节点的任意树,没有顺序规则。二叉搜索树额外要求左子树的值都比节点小、右子树的值都比节点大,正是这条规则让快速查找成为可能。
为什么二叉搜索树会变慢?
O(n)。自平衡树(AVL 树、红黑树)通过旋转节点来避免这种情况。二叉搜索树和哈希表有什么区别?
O(1),但键的存储没有特定顺序,所以无法回答范围查询或后继查询。二叉搜索树稍慢,是 O(log n),但它让键保持有序,你可以按顺序遍历它们,也能找到最接近的值。顺序重要时选 BST,只需判断是否存在时选哈希表。什么时候该用自平衡树而不是普通 BST?
O(log n) 的性能时,就用自平衡树(AVL 树、红黑树)。普通 BST 适合教学、小数据集,或键以随机顺序到达的场景,但它无法防止倾斜带来的最坏情况。BST 的中序遍历会返回有序的值吗?
会。先访问左子树,再访问节点本身,最后访问右子树,就能按升序得到所有键,这直接来自 BST 的规则。一个常见的错误是对普通二叉树抱有同样的期待:没有顺序规则,中序遍历得到的序列没有任何意义。