二叉搜索树
前言
字典抽象数据类型 (Dictionary Abstract Data Type) 用于表示一组 “键 - 值对” 元素,它支持三大核心操作:
Search(S, k):在集合 S 中查找键为 k 的元素,返回对应的值Insert(S, x):向集合 S 中插入值为 x 的元素(若键已存在,需考虑更新为新值)Remove(S, x/k):从集合 S 中删除值为 x / 键为 k 的元素
特别地,如果键是有序的(称有序字典),则可支持以下操作:
Min(S):返回集合 S 中键最小的元素Max(S):返回集合 S 中键最大的元素Predecessor(S, x/k):返回集合 S 中键小于 k / 值小于 x 的最大元素Successor(S, x/k):返回集合 S 中键大于 k / 值大于 x 的最小元素
如何高效实现有序字典 ADT 的数据结构即接下来探讨的问题
概念
定义:一种满足 “左子树所有节点值 <= 根节点值,右子树所有节点值 >= 根节点值” 的二叉树,左、右子树也需满足该规则
核心性质:中序遍历可得到有序序列,这是其用于排序、查找的核心依据
插入、查找、删除的时间复杂度均为 \(O (h)\)(\(h\) 为树的高度,平均为 \(\log n\),但极端情况下会退化为链表(如插入有序数据时 \(h=n\)),此时复杂度降为 \(O (n)\))
节点
struct BSTNode
{
int key;
// type val;
BSTNode *left;
BSTNode *right;
BSTNode *parent;
explicit BSTNode(int k):key(k),left(nullptr),right(nullptr),parent(nullptr){}
};
查找
从根节点开始,若当前节点为空或键值等于目标值 k,则返回当前节点;若当前节点键值大于 k,则递归查找左子树,否则递归查找右子树
class BST
{
BSTNode *root; // 根节点
BSTNode *BSTSearchRecursive(BSTNode *node,int k)
{
if(node == nullptr || node->key == k) // 括号内的判断有先后关系
{
return node;
}
if(node->key > k)
{
return BSTSearchRecursive(node->left,k);
}
else
{
return BSTSearchRecursive(node->right,k);
}
}
public:
BST():root(nullptr){}
BSTNode *BSTSearchRecursive(int k) // 对外接口
{
return BSTSearchRecursive(root,k);
}
};
当然也可以用迭代实现,相比递归居然更简洁
class BST
{
// ···
BSTNode *BSTSearchIterative(int k)
{
BSTNode *curr = root;
while(curr != nullptr && curr->key != k)
{
if(k < curr->key)
{
curr = curr->left;
}
else
{
curr = curr->right;
}
}
return curr;
}
};
最小、最大
由二叉搜索树的性质可知,最小值一定在左子树的最左端,最大值一定在右子树的最右端
class BST
{
// ···
BSTNode* BSTMin(BSTNode* node)
{
BSTNode *curr = node;
while(curr->left != nullptr)
{
curr = curr->left;
}
return curr;
}
BSTNode* BSTMax(BSTNode* node)
{
BSTNode *curr = node;
while(curr->right != nullptr)
{
curr = curr->right;
}
return curr;
}
// 当然也可以保留一个无参版本作为对外接口,传入根节点
};
后继、前驱
后继节点为比当前节点大的最小节点,由中序遍历的有序性,根据当前节点有无右子树,后继节点可能为
- 有右子树:右子树的最小节点
- 无右子树:其最近的祖先中左子节点也是该节点祖先者(第一个要“左拐”的祖先)
x y y // 查找 x 的后继,在这里为 y
/ \ / \ / \ // s 表示子树,在这里不关心
s a x s a s
/ \ / / \
y s s s x
/
s
中序遍历:s x y a s、s x y s、s a s x y s
class BST
{
// ···
BSTNode *BSTSuccessor(BSTNode *node)
{
if(node == nullptr)
{
return nullptr;
}
// 如果有右子树
if(node->right != nullptr)
{
return BSTMin(node->right);
}
// 如果没有右子树
while(node->parent != nullptr && node == node->parent->right)
{
node = node->parent;
}
return node->parent;
}
};
前驱与后继同理,只不过左右方向相反
插入
为了保存有序性,实则插入也类似做搜索,只不过会停止在某个叶子节点上,然后将待插元素作为该叶子节点的左或右子节点
class BST
{
// ···
void BSTInsert(int k)
{
BSTNode *newNode = new BSTNode(k);
BSTNode *parent = nullptr; // 保留父节点
BSTNode *curr = root; // 遍历完为空
while(curr != nullptr) // 查找
{
parent = curr;
if(k <= curr->key)
{
curr = curr->left;
}
else
{
curr = curr->right;
}
}
newNode->parent = parent; // 插入
if(parent == nullptr)
{
root = newNode;
}
else if(k <= parent->key)
{
parent->left = newNode;
}
else
{
parent->right = newNode;
}
}
};
删除
设待删节点为 x,为了保持二叉搜索树的有序性,删除分为三种情况:
x无子节点,直接删除x只有一个子节点,将子节点 “替换”xx有两个子节点,用后继BSTMin(x->right)替换x- 后继是
x的直接右子节点(该右子节点无左节点),直接用右子节点替换x - 后继不是
x的直接右子节点,用后继替换x,并将后继的右子节点替换后继
- 后继是
case 2:
r r
/ \ / \
x s -> w s
/
w
case 3a:
r r
/ \ / \
x s -> y s
/ \ / \
w y w a
\
a
case 3b:
r r
/ \ / \
x s y s
/ \ -> / \
w z w z
/ \ / \
y a b a
\
b (也可以不存在)
class BST
{
// 辅助函数:用新节点替换旧节点
void transplant(BSTNode* u, BSTNode* v) {
if (u->parent == nullptr) {
root = v; // u是根节点
} else if (u == u->parent->left) {
u->parent->left = v;
} else {
u->parent->right = v;
}
if (v != nullptr) {
v->parent = u->parent;
}
}
void BSTRemove(BSTNode* node) {
if (node == nullptr) return;
// 无左子节点(包括无子节点和只有右子节点)
if (node->left == nullptr) {
transplant(node, node->right);
delete node;
}
// 无右子节点(只有左子节点)
else if (node->right == nullptr) {
transplant(node, node->left);
delete node;
}
// 有两个子节点
else {
// 找到后继节点(右子树的最小节点)
BSTNode* successor = BSTMin(node->right);
// 如果后继节点不是 node 的直接右子节点
if (successor->parent != node) {
transplant(successor, successor->right);
successor->right = node->right;
successor->right->parent = successor;
}
// 用后继节点替换 node
transplant(node, successor);
successor->left = node->left;
successor->left->parent = successor;
delete node;
}
}
};
拓展
BST 的旋转
旋转分为左旋和右旋,用以调整 BST 的结构,使其更平衡
性质:选转前后不改变树的中序遍历结果,即仍保持有序性
- 对 A 左旋,中序遍历:T1 A T2 B T3
A B
/ \ / \
T1 B -> A T3
/ \ / \
T2 T3 T1 T2
实现:更新 A 的右子树为 B 的左子树,调整 A 和 B
class BST
{
void rotateLeft(BSTNode *A){
BSTNode *B = A->right;
A->right = B->left; // 更新 A 的右子树
if(B->left != nullptr){
B->left->parent = A;
}
B->parent = A->parent; // B 继承 A 的父节点
if(A->parent == nullptr){
root = B;
}
else if(A == A->parent->left){
A->parent->left = B;
}
else{
A->parent->right = B;
}
B->left = A; // 调整 A B 的父子关系
A->parent = B;
}
};
- 对 A 右旋,中序遍历:T1 B T2 A T3
A B
/ \ / \
B T3 -> T1 A
/ \ / \
T1 T2 T2 T3
实现:更新 A 的左子树为 B 的右子树,调整 A 和 B
class BST
{
void rotateRight(BSTNode *A){
BSTNode *B = A->left;
A->left = B->right; // 更新 A 的左子树
if(B->right != nullptr){
B->right->parent = A;
}
B->parent = A->parent; // B 继承 A 的父节点
if(A->parent == nullptr){
root = B;
}
else if(A == A->parent->left){
A->parent->left = B;
}
else{
A->parent->right = B;
}
B->right = A; // 调整 A B 的父子关系
A->parent = B;
}
};
升序数组 => 平衡 BST
平衡二叉树是指所有节点的左右子树的高度相差不超过 1 的二叉树
TreeNode* sortedArrayToBST(vector<int>& nums) {
return helper(nums, 0, nums.size() - 1);
}
TreeNode* helper(vector<int>& nums, int left, int right) {
if (left > right) {
return nullptr;
}
// 选择中间位置作为根节点
int mid = (left + right) / 2;
TreeNode* root = new TreeNode(nums[mid]);
root->left = helper(nums, left, mid - 1);
root->right = helper(nums, mid + 1, right);
return root;
}
标题:二叉搜索树
作者:Zwing
创建于:2026-01-16 19:04:31
更新于:2026-08-07 16:17:52
链接:https://zanytriumph.github.io/posts/二叉搜索树.html
版权声明:本文章采用 CC BY-NC-SA 4.0 进行许可