二叉搜索树是为了更高效的查找、插入和删除,并不是为了对关键字进行排序。他的定义如下:
- 空树也是二叉排序树
- 若右子树不为空,则右子树的所有节点值都大于根节点
- 若左子树不为空,则左子树的所有节点值都大于根节点
- 二叉排序树的左右子树都是二叉排序树
二叉排序树(二叉搜索树)的节点定义如下:
struct TreeNode {
int key;
TreeNode* left;
TreeNode* right; // 维护其他信息,如高度,节点数量等
int size; // 当前节点为根的子树大小
int count; // 当前节点的重复数量
TreeNode(int value) : key(value), size(1), count(1), left(nullptr), right(nullptr) {}
};
搜索树的遍历
void inorderTraversal(TreeNode* root) {
if (root == nullptr) { return; }
inorderTraversal(root->left);
std::cout << root->key << " ";
inorderTraversal(root->right);
}
由中序遍历出的二叉搜索树的序列都是递增序列,该中序遍历的事件复杂度为O(n)
搜索树的查找
代码与遍历方式基本相同,若当前所处结点的值大于目标数则到左子树查找,若小于目标树则到右子树查找,直到找到目标树。如果未找到直接返回NULL即可,我们使用非递归的方式再实现一遍上述代码逻辑。
bool search(TreeNode* root, int target) {
TreeNode* current = root;
if (current->key == target) { return true; }
while(current != NULL && current->key != target) {
if(target < current->key) current = current->left;
else if(target > current->key) current = current->right;
else return true;
}
return false;
}
时间复杂度为O(h)
搜索树的插入
在二叉搜索树root中插入一个value时,可能出现以下几种情况
- root为null,直接插入
- value小于root权值,在root的左子树插入value结点
- value大于root权值,在root的右子树插入value结点
- value等于root权值,让root的count++
TreeNode* insert(TreeNode* root, int value) {
if (root == nullptr) {
return new TreeNode(value);
}
if (value < root->key) {
root->left = insert(root->left, value);
} else if (value > root->key) {
root->right = insert(root->right, value);
} else {
root->count++; // 节点值相等,增加重复数量
}
root->size = root->count + (root->left ? root->left->size : 0) +
(root->right ? root->right->size : 0); // 更新节点的子树大小
return root;
}
时间复杂度为O(h)
搜索树的删除
在删除搜索中的value时,需要先在书中find到该结点,删除时会出现以下情况:
- 该点的count>1,直接让count--就可以
- 结点为叶子结点,直接删除即可。
- 结点只有一个儿子,直接让他的儿子代替他即可
- 结点有两个儿子,让左子树的最大值或者时右子树的最小值代替这个结点
TreeNode* findMin(TreeNode* curr) {
while(curr->left != nullptr) {
curr = curr->left;
}
return curr;
}
TreeNode* remove(TreeNode* root, int value) {
if(root == nullptr) {
return root;
}
if(value < root->key) {
root->left = remove(root->left, value);
} else if(value > root->key) {
root->right = remove(root->right, value);
} else {
// value == root->key
if(root->count > 1) {
root->count --;
} else {
// 删除该节点
if(root->left == nullptr) {
TreeNode* temp = root->right;
delete root;
return temp;
} else if(root->right == nullptr) {
TreeNode* temp = root->left;
delete root;
return temp;
} else {
// 查找右子树的最小值
TreeNode* success = findMin(root->right);
root->count = success->count;
root->key = success->key;
// 赋值完后进行删除
root->right = remove(root->right, success->key);
}
}
}
return root;
}
效率分析
通过分析上面的操作可以看出二叉排序树与二分查找有点像,但是二分查找的判定树是唯一的,二叉排序树不唯一
二叉排序树的平均查找长度。 其中为第i层的高度、是第i层的数量、nums是总的节点数量。
插入、删除时间复杂度