二叉搜索树(BST)是一种高效的数据结构,它保持了元素的顺序性,使得查找、插入和删除操作都能在对数时间内完成。今天我将结合LeetCode的三道经典题目,深入剖析BST的核心操作实现细节。
BST的核心特性是:对于任意节点,其左子树所有节点值小于该节点值,右子树所有节点值大于该节点值。这个特性使得我们可以利用二分查找的思想来设计算法。
在实际应用中,BST的平均时间复杂度为O(log n),最坏情况下(退化成链表)为O(n)。因此平衡二叉搜索树(如AVL树、红黑树)在实际工程中更为常用。
给定二叉搜索树和两个节点p、q,找到它们的最低公共祖先(LCA)。LCA是同时为p和q祖先的节点中深度最大的那个。
cpp复制TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (p->val < root->val && q->val < root->val)
return lowestCommonAncestor(root->left, p, q);
if (p->val > root->val && q->val > root->val)
return lowestCommonAncestor(root->right, p, q);
return root;
}
这个递归解法利用了BST的有序性:
递归解法可以很容易地转换为迭代版本,减少函数调用开销:
cpp复制TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
while(root) {
if(p->val < root->val && q->val < root->val)
root = root->left;
else if(p->val > root->val && q->val > root->val)
root = root->right;
else
return root;
}
return nullptr;
}
注意:在实际工程中,迭代版本通常性能更好,且不会出现栈溢出问题。
cpp复制TreeNode* insertIntoBST(TreeNode* root, int val) {
if(!root) return new TreeNode(val);
if(val < root->val)
root->left = insertIntoBST(root->left, val);
else
root->right = insertIntoBST(root->right, val);
return root;
}
这个实现体现了BST插入的核心逻辑:
cpp复制TreeNode* insertIntoBST(TreeNode* root, int val) {
if(!root) return new TreeNode(val);
TreeNode* curr = root;
while(true) {
if(val < curr->val) {
if(!curr->left) {
curr->left = new TreeNode(val);
break;
}
curr = curr->left;
} else {
if(!curr->right) {
curr->right = new TreeNode(val);
break;
}
curr = curr->right;
}
}
return root;
}
迭代版本避免了递归调用的开销,对于大型树性能更好。同时,我们可以记录父节点指针来简化实现。
节点删除是BST操作中最复杂的,需要考虑三种情况:
cpp复制TreeNode* deleteNode(TreeNode* root, int key) {
if(!root) return nullptr;
if(key > root->val) {
root->right = deleteNode(root->right, key);
} else if(key < root->val) {
root->left = deleteNode(root->left, key);
} else {
if(!root->left) return root->right;
if(!root->right) return root->left;
TreeNode* minNode = findMin(root->right);
root->val = minNode->val;
root->right = deleteNode(root->right, minNode->val);
}
return root;
}
TreeNode* findMin(TreeNode* node) {
while(node->left) node = node->left;
return node;
}
虽然上述实现能正确删除节点,但可能导致树的不平衡。在实际工程中,我们通常会使用平衡二叉搜索树来实现删除操作,以保持树的平衡性。
重要提示:在面试中,面试官可能会要求解释删除操作的时间复杂度。最坏情况下(如删除根节点且树退化成链表),时间复杂度为O(n)。
在实现BST算法时,特别需要注意以下边界条件:
在C++实现中,特别是删除操作时,需要注意:
在调试BST相关代码时,可以编写一个辅助函数验证BST的有效性:
cpp复制bool isValidBST(TreeNode* root) {
return isValidBST(root, nullptr, nullptr);
}
bool isValidBST(TreeNode* node, TreeNode* min, TreeNode* max) {
if(!node) return true;
if(min && node->val <= min->val) return false;
if(max && node->val >= max->val) return false;
return isValidBST(node->left, min, node) &&
isValidBST(node->right, node, max);
}
虽然标准BST不保证平衡,但我们可以采取一些策略减少退化:
在实际工程中,BST常用于:
我在实际项目中使用BST时发现,对于频繁更新的数据集,平衡二叉搜索树的性能优势非常明显。曾经在一个需要实时维护百万级有序数据的项目中,将普通BST替换为红黑树后,性能提升了近10倍。