二叉搜索树(Binary Search Tree,BST),要么是一颗空树,要么是一颗具有如下性质的二叉树:对于二叉搜索树的任意结点来说,若它的左子树非空,则左子树上所有结点的值均小于它的值; 若它的右子树不空,则右子树上所有结点的值均大于它的值; 它的左、右子树也分别为二叉搜索树。
定义一颗二叉搜索树的结点结构如下:
struct BST_Node
{
int val;
BST_Node *left;
BST_Node *right;
BST_Node(int x) : val(x), left(nullptr), right(nullptr){}
};
基于二叉搜索树的特点,其查找结点值的方式与普通二叉树不同。为了维护二叉搜索树的特点,其结点的插入、删除操作也比较特殊。
查找
二叉搜索树的查找比较简单,结合二叉搜索树左子树比根小,右子树比根大的特点,只需要将查找值与结点值作比较,判断下一步往左子树走还是右子树走就可以了。
BST_Node* searchBST(BST_Node *root, int val)
{
while (root != nullptr)
{
if (root->val < val)
{
root = root->right;
}
else if (root->val > val)
{
root = root->left;
}
else
{
break;
}
}
return root;
}
递归版本:
BST_Node* searchBST(BST_Node *root, int val)
{
if (root == nullptr)
{
return root;
}
if (root->val < val)
{
return searchBST(root->right, val);
}
else if (root->val > val)
{
return searchBST(root->left, val);
}
return root;
}
和二分查找中每次迭代之后查找的区间就会减半一样,在二叉查找树中,随着我们不断向下查找,当前结点所表示的子树大小也在减小(理想情况下是减半,但至少会减少一个结点)。当找到一个含有被查找的键的结点或者当前子树为空时才会结束。
插入
二叉搜索树的一个重要特性之一就是插入的实现难度和查找差不多。按照查找规则,找到合适的位置插入结点即可。
BST_Node* insertBST(BST_Node *root, int val)
{
if (root == nullptr)
{
root = new BST_Node(val);
return root;
}
BST_Node *parent = nullptr, *cur = root;
while (cur != nullptr)
{
parent = cur;
if (cur->val < val)
{
cur = cur->right;
}
else if (cur->val > val)
{
cur = cur->left;
}
else
{
return nullptr;
}
}
if (parent->val < val)
{
parent->right = new BST_Node(val);
return parent->right;
}
else
{
parent->left = new BST_Node(val);
return parent->left;
}
return nullptr;
}
递归版本较为简洁:
BST_Node* insertBST(BST_Node *root, int val) {
if (root == nullptr)
{
root = new BST_Node(val);
return root;
}
if (root->val < val)
{
return insertBST(root->right, val);
}
else if (root->val > val)
{
return insertBST(root->left, val);
}
return nullptr;
}
删除
二叉搜索树的结点删除较难实现,首先我们先要找到待删除的结点,若不存在则返回空,否则需要分为以下4种情况:
·待删除结点没有左右子树的情况最简单,直接删除该结点即可。
·待删除结点只有左子树非空,这种情况将其左子树的根结点接上其父结点即可。
·待删除结点只有右子树非空,这种情况将其右子树的根结点接上其父结点即可。
·待删除结点的的左右子树都非空,这种情况较复杂,需要选择一个新结点取代待删除结点的位置。待删除结点的右子树中的最左结点(即大于待删除结点值的最小结点值)符合保持二叉搜索树特性的要求。找到这个后继结点后,让后继结点代替待删除结点成为新的根结点(值覆盖即可),然后将后继结点的右子树接入后继结点的父结点。
void deleteBST_Node(BST_Node *root, int val)
{
if (root == nullptr)
{
return ;
}
BST_Node *parent = nullptr, *cur = root;
while (cur != nullptr)
{
if (cur->val < val)
{
parent = cur;
cur = cur->right;
}
else if (cur->val > val)
{
parent = cur;
cur = cur->left;
}
else { //找到待删除结点
BST_Node *del = cur;
if (cur->left == nullptr && cur->right == nullptr) //左右子树都为空
{
if (parent->left == cur)
parent->left = nullptr;
else if (parent->right == cur)
parent->right = nullptr
delete cur;
}
else if (cur->left != nullptr && cur->right != nullptr) //左右子树都不为空
{
del = cur->right;
while (del->left != nullptr)
{
parent = del;
del = del->left;
}
cur->val = del->val;
if (del == cur->right)
{
parent->right = del->right;
}
else
{
parent->left = del->right;
}
delete del;
}
else //左右子树其中一个为空
{
if (cur->left != nullptr)
{
if (parent->left == cur)
{
parent->left = cur->left;
}
else
{
parent->right = cur->left;
}
}
else
{
if (parent->left == cur)
{
parent->left = cur->right;
}
else
{
parent->right = cur->right;
}
}
delete cur;
}
break;
}
}
}