概念
- 若左子树不为空,则左子树上所有结点的值都小于根结点
- 若右子树不为空,则右子树上所有结点的值都小于根结点
- 它的左右子树也分别为二叉搜索树
- 空树也是二叉搜索树
二叉搜索树结点的定义
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
, _right(nullptr)
, _key(key)
{}
};
二叉搜索树类定义
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
void _Inorder(Node* root);
void Inorder();
bool Insert(const K& key);
Node* Find(const K& key);
bool Erase(const K& key);
private:
Node* _root = nullptr;
};
二叉搜索树的中序遍历
void _Inorder(Node* root)
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_key << ' ';
_Inorder(root->_right);
}
void Inorder()
{
_Inorder(_root);
cout << endl;
}
由于在类的成员变量_root外部无法访问, 所以再定义一个_Inorder函数
二叉搜索树的插入
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
// 在二叉树中搜索要插入的位置,最后cur为空,就是要插入的位置,此时parent为cur的父节点。
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
// 当key大于结点,往右搜索要插入的位置
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
// 当key小于结点,往左搜索要插入的位置
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
// 当key等于结点,那么不插入,返回false
else
{
return false;
}
}
// cur此时为空结点,该位置就是要插入的位置
cur = new Node(key);
// 父节点小于key,插入到父节点的右边
if (parent->_key < key)
{
parent->_right = cur;
}
// 父节点大于key,插入到父节点的左边
else
{
parent->_left = cur;
}
return true;
}
如果二叉树中有一个结点等于key,那么就不插入,返回false。
二叉树搜索树的查找
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
二叉搜索树的删除
bool Erase(const K& key)
{
//找到要删除的结点之后,需要该节点的父结点才能删除该结点
Node* parent = nullptr;
Node* cur = _root;
//在树中寻找要删除的结点
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
//找到了要删除的结点,cur
//分为三种情况删除
//1、左为空,父亲指向cur的右
//2、右为空,父亲指向cur的左
//3、左右都不为空,
if (cur->_left == nullptr)
{
// 当要删除的结点为根结点时
if (cur == _root)
{
_root = cur->_right;
}
else
{
// 当删除的结点为右结点时
if (parent->_right == cur)
parent->_right = cur->_right;
// 当删除的结点为左结点时
else
parent->_left = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{
// 当要删除的结点为根结点时
if (cur == _root)
{
_root = cur->_left;
}
else
{
// 当删除的结点为右结点时
if (parent->_right == cur)
parent->_right = cur->_left;
// 当删除的结点为左结点时
else
parent->_left = cur->_left;
}
delete cur;
}
// 删除的结点左右都不为空
else
{
//找右子树的最左结点,将该结点覆盖要删除的结点
// 当然也可以找左子树最右结点
Node* rightMinParent = cur;
Node* rightMin = cur->_right;
// 循环找右子树最左的结点
while (rightMin->_left)
{
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
//替代
// cur:要删除的结点;rightMin:右子树最小的结点,直接覆盖cur
cur->_key = rightMin->_key;
//删除
// 此时rightMin为叶子结点
if (rightMin == rightMinParent->_left)
rightMinParent->_left = rightMin->_right;
else
rightMinParent->_right = rightMin->_right;
delete rightMin;
}
return true;
}
}
//此时树结点都找完了,但是没有要删除的结点,返回false
return false;
}
删除分为三种情况:(其实还有一种:删除的结点左右都为空。这种情况直接可以当成1or2处理)
- 删除的结点左为空
- 删除的结点右为空
- 删除的结点左右都不为空
注意:删除一个结点,需要找到该结点的父结点才能进行删除操作。所以在情况1or2,要对删除的结点是否为根结点特判。
情况3是最复杂的一个,需要找到该结点的右子树的最左结点进行覆盖,然后删除最左结点(当然寻找左子树的最右结点也行)。所以情况3不需要对根结点进行特判。
对于情况3,找左子树的最右结点
Node* leftMinParent = cur;
Node* leftMin = cur->_left;
while (leftMin->_right)
{
leftMinParent = leftMin;
leftMin = leftMin->_right;
}
cur->_key = leftMin->_key;
if (leftMin == leftMinParent->_left)
leftMinParent->_left = leftMin->_left;
else
leftMinParent->_right = leftMin->_left;
delete leftMin;
二叉搜索树的KV模型应用
二叉树搜索树的每一个结点还可以存一个值(value),这样就可以用key去映射这个value值,从而可以应用在一些场景。
整体的代码实现就是在二叉搜索树的结点种添加一个value。
#pragma once
#include<string>
#include<iostream>
using namespace std;
template<class K, class V>
struct BSTreeNode
{
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
K _key;
V _value;
BSTreeNode(const K& key, const V& value)
:_left(nullptr)
, _right(nullptr)
, _key(key)
, _value(value)
{}
};
template<class K, class V>
class BSTree
{
typedef BSTreeNode<K, V> Node;
public:
bool Insert(const K& key, const V& value)
{
if (_root == nullptr)
{
_root = new Node(key, value);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key, value);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
void _Inorder(Node* root)
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_key << ' ' << root->_value << ' ';
_Inorder(root->_right);
}
void Inorder()
{
_Inorder(_root);
cout << endl;
}
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
// 找到了要删除的结点,cur
// 分为三种情况删除
//1、左为空,父亲指向cur的右
//2、右为空,父亲指向cur的左
//3、左右都不为空,
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_right == cur)
parent->_right = cur->_right;
else
parent->_left = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_right == cur)
parent->_right = cur->_left;
else
parent->_left = cur->_left;
}
delete cur;
}
else
{
// 找右子树的最左结点,将该结点覆盖要删除的结点
Node* rightMinParent = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
//替代
cur->_key = rightMin->_key;
//删除
if (rightMin == rightMinParent->_left)
rightMinParent->_left = rightMin->_right;
else
rightMinParent->_right = rightMin->_right;
delete rightMin;
}
return true;
}
}
return false;
}
private:
Node* _root = nullptr;
};
二叉搜索树的优缺点
由于二叉搜索树的性质,增、删、查、找(注意不能改,因为改结点可能不能继续维持一颗二叉搜索树了),都是和查找的性能相关,查找的时间复杂度:O(log n)。但是当插入一个有序或者接近有序的数组,那么就会形成一颗单边树,这样就和链表一样了。为了优化这一情况,就引入了AVLTree和红黑树。后面会在我的博客中和大家介绍。