-
(Binary Search Tree,简称 BST),也常被称为二叉查找树或二叉排序树。它是二叉树中最常用的一种变体,专门为快速查找、插入和删除数据而设计。
1. BST 的核心性质
一棵 BST 必须满足以下三个性质:
-
左子树上所有结点的值均小于它的根结点的值。
-
右子树上所有结点的值均大于它的根结点的值。
-
左、右子树也分别为二叉搜索树。
-
(通常情况下)树中不包含重复的节点。
重要结论: 对一棵 BST 进行中序遍历(Left -> Root -> Right),得到的结果一定是一个升序排列的序列。这是检验一棵树是否为 BST 的最直观方法。
2. 核心操作详解
A. 查找 (Search)
从根节点开始:
-
如果目标值等于当前节点值,查找成功。
-
如果目标值小于当前节点值,则进入左子树继续查找。
-
如果目标值大于当前节点值,则进入右子树继续查找。
-
如果走到空节点,说明目标值不存在。
B. 插入 (Insertion)
插入操作总是从根节点开始,逻辑与查找类似。新节点最终会被插入到某个叶子节点的位置,从而保持 BST 的性质。
C. 删除 (Deletion) —— 最复杂的操作
删除节点需要分三种情况讨论,以确保删除后树依然满足 BST 性质:
-
被删节点是叶子节点: 直接删除,将其父节点指向它的指针置为空。
-
被删节点只有一个孩子: 将该节点的父节点直接指向它的那个孩子(类似链表删除)。
-
被删节点有两个孩子: * 找到该节点的中序后继(右子树中最小的节点)或中序前驱(左子树中最大的节点)。
-
用后继(或前驱)节点的值替换掉要删除节点的值。
-
最后删除那个多余的后继(或前驱)节点。
-
3. 性能分析
BST 的效率极大地依赖于树的高度 (h):
操作 平均时间复杂度 最坏时间复杂度 (退化为链表) 查找 O(\log n) O(n) 插入 O(\log n) O(n) 删除 O(\log n) O(n) -
理想状态: 树是平衡的(接近满二叉树),高度 h = \log n。
-
最坏状态: 插入的数据是有序的(如 1, 2, 3, 4, 5),树会退化成一条斜树(链表),此时查找效率降为 O(n)。
4. 代码实现
using namespace std; // 定义节点结构 struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; class BST { public: BST() : root(nullptr) {} // 插入接口 void insert(int val) { root = insert(root, val); } // 查找接口 bool search(int val) { return search(root, val) != nullptr; } // 删除接口 void remove(int val) { root = remove(root, val); } // 中序遍历(打印排序结果) void inorder() { inorder(root); cout << endl; } private: TreeNode* root; // 递归插入 TreeNode* insert(TreeNode* node, int val) { if (node == nullptr) return new TreeNode(val); if (val < node->val) node->left = insert(node->left, val); else if (val > node->val) node->right = insert(node->right, val); return node; } // 递归查找 TreeNode* search(TreeNode* node, int val) { if (node == nullptr || node->val == val) return node; if (val < node->val) return search(node->left, val); return search(node->right, val); } // 查找最小值节点(删除操作辅助函数) TreeNode* findMin(TreeNode* node) { while (node && node->left) node = node->left; return node; } // 递归删除(最核心部分) TreeNode* remove(TreeNode* node, int val) { if (node == nullptr) return nullptr; if (val < node->val) { node->left = remove(node->left, val); } else if (val > node->val) { node->right = remove(node->right, val); } else { // 找到目标节点了! // 情况 1 & 2: 叶子节点或只有一个孩子 if (node->left == nullptr) { TreeNode* temp = node->right; delete node; return temp; } else if (node->right == nullptr) { TreeNode* temp = node->left; delete node; return temp; } // 情况 3: 有两个孩子 // 找到右子树中最小的节点(中序后继) TreeNode* temp = findMin(node->right); // 替换值 node->val = temp->val; // 删除掉右子树中那个多余的后继节点 node->right = remove(node->right, temp->val); } return node; } // 递归中序遍历 void inorder(TreeNode* node) { if (node == nullptr) return; inorder(node->left); cout << node->val << " "; inorder(node->right); } }; // 测试代码 int main() { BST tree; // 插入测试 int nums[] = {50, 30, 20, 40, 70, 60, 80}; for (int x : nums) tree.insert(x); cout << "中序遍历结果(应为升序): "; tree.inorder(); // 查找测试 cout << "查找 40: " << (tree.search(40) ? "存在" : "不存在") << endl; cout << "查找 90: " << (tree.search(90) ? "存在" : "不存在") << endl; // 删除测试 cout << "删除 20 (叶子节点)..." << endl; tree.remove(20); tree.inorder(); cout << "删除 30 (有一个孩子)..." << endl; tree.remove(30); tree.inorder(); cout << "删除 50 (有两个孩子,根节点)..." << endl; tree.remove(50); tree.inorder(); return 0; }
5. 进阶:如何解决“退化”问题?
由于普通 BST 在极端情况下会退化为链表,为了保证 O(\log n) 的性能,工程师们开发了自平衡二叉搜索树:
-
AVL 树: 严格平衡,任何节点的左右子树高度差不超过 1。
-
红黑树 (Red-Black Tree): 弱平衡,但在插入和删除时旋转次数较少。它是 Java 的
TreeMap、TreeSet以及 C++ STL 中map
-