一、二叉搜索树的定义
二叉搜索树是一颗特殊的二叉树,通常称它为二叉排序树,它具有以下的特点:
- 如果这棵树的左子树不为空,则左子树的所有结点都要小于他的根结点。
- 如果这棵树的右子树不为空,则右子树的所有结点都要大于他的根结点。
- 它的左右子树依旧满足上面的特点。
- 这颗二叉树既可以支持插入相同的元素也可以支持不能插入相同的元素。具体的要看使用场景,这里为我们后面将要学习的set/mulitset,map/mulitmap做一下铺垫。

二、二叉搜索树的使用场景
既然这颗特殊的二叉树被称为搜索树,那么它的主要作用肯定就是在搜索场景中进行使用。
2.1、二叉搜索树的效率分析
我们之前学习过普通的二叉树,没有进行深入的学习,原因就在于单纯的一颗二叉树没有任何的意义,只有我们对它进行一些控制才能真正的体现他的价值。二叉搜索树就是如此,它的搜索效率高达了logn,这是什么概念呢?我们可以这样算一下,100000个数据使用二叉搜索树仅仅需要查询17次,而1000000000仅仅需要30次左右,十亿个数据的查询如果使用二叉搜索树仅仅需要30次左右,这是非常厉害的。
但是所有的二叉搜索树的查询的效率都是这样的高的吗?很显然不是的,下面的这颗二叉搜索树就不是这样的:

对于这颗二叉搜索树而言它的查询效率逼近与O(n),所以说不是所有的二叉搜索树都是满足logn的查询效率的,对于上面这种较差的二叉搜索树我们后面学习他的变形像AVL树和红黑树。
还有有需要了解的一点就是二分查找算法也是可以达到logn的时间复杂度但是为什么还要有二叉搜索树呢,下面介绍一下二分查找具有两大缺陷:
- 首先就是二分查找是基于像数组这样的可以支持下标的随机访问的数据结构进行使用,并且最重要的是如果数据是无序的首先要进行排序,只有有序的数据才能使用二分查找。
- 其次二分查找的数据结构由于内存连续的特点在插入和删除数据时由于要挪动数据时间复杂度就提升到了O(n),对于需要大量挪动数据场景二分查找算法显然很难胜任二叉搜索树。
通过比较二者的差距,我们便可以发现二叉搜索树的价值所在。
2.2、二叉搜索树的使用场景
2.2.1、key搜索场景(等价于set容器的作用)
在这种场景下,二叉搜索树中仅仅需要存储一个key关键码,key关键码即为我们查找的对象。对于这种类型的二叉树是支持增删查操作的,但是不支持改的操作。因为二叉搜索树本事是又属于自己的结构如果我们修改了其中的数据那么它的结构将不复存在。
使用场景一:一个小区的停车场,买了车位的业主才可以进入停车场,物业将买了车位的业主的车辆信息存入后台系统。当有车来时,扫描该车的车牌号,如果存在则抬杆允许进入,如果不在则不会抬杆。
使用场景二:检查单词的拼写是否正确,即将词库中的所有单词存入搜索树中,然后读取文章中的单词,如果存在则正确否则就是拼写错误。
2.2.2、key/value搜索场景(等价于map容器的作用)
这种场景下,二叉搜索树中不仅仅需要存入一个key值还需要一个value值。每一个key关键码都会对应一个value值,value可以是任意类型的对象。这种场景可以通过key关键码快速的找到value值。这种搜索树支持增删查的操作,它还支持修改value的值,但是依然不支持修改key的值,因为修改key的值依然会破坏搜索树的结构。
使用场景一:英汉互译的词典,使用英语作为关键码key使用汉语作为value值,输入英文可以快速的查找的对应的中文意思。
使用场景二:统计单词的个数,使用英语作为关键码key使用一个统计个数的变量作为value,如果该单词不存在就先插入然后++value值,如果存在就++value值。
三、模拟实现二叉搜索树(不支持冗余数据)
3.1、二叉搜索树的插入操作
- 判断树是否为空即根结点_root是否为空,如果为空就插入。
- 如果树不为空的话就和根结点进行比较,如果大于根结点就向右继续进行比较,否则向左进行比较直到找到空的位置插入该结点。
- 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。(要注意的是要保持逻辑⼀致性,插入相等的值不要⼀会往右走,一会往左走)
bool insert(key_type key)
{
// 1.树为空
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
else {
// 2.树不为空
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key == key)
{
return false;
}
else {
parent = cur;
cur = cur->_right;
}
}
cur = new Node(key);
if (parent->_key > key)
{
parent->_left = cur;
}
else {
parent->_right = cur;
}
return true;
}
}
3.2、二叉树的查找操作
- 同样先判断树是否为空,如果为空直接返回false。
- 当树不为空的时候,遍历二叉树,如果元素大于cur->_key就向右查找,小于就向左查找,等于直接返回true。
- 如果查找的这个数是不存在的就最多查找高度次,最后返回false。
- 如果支持插入相等的值则返回中序遍历中的第一个目标值即可。
bool find(const key_type& key)
{
if (_root == nullptr)
{
return false;
}
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
cur = cur->_left;
}
else if (cur->_key == key)
{
return true;
}
else {
cur = cur->_right;
}
}
return false;
}
3.3、二叉树的删除操作
- 第一步首先判断一下二叉树中是否存在我们要删除的目标元素,如果不存在的话直接返回false。
- 当二叉树中存在我们的目标元素时,我们一共有下面四种情况:
- 第一种,要删除的左右孩子都为空。这种情况下,我们直接将该结点的父节点对应该结点的孩子指针指向空,然后直接删除该节即可。
- 第二种,要删除的左孩子为空,右孩子不为空。这种情况下,我们考虑一下二叉搜索树的特点,它的左子树的所有结点都是小于根结点的,右子树的所有结点都是大于根结点的。那么在这种特征下,我们要删除的结点的左孩子为空右孩子不为空,我们只需要将该结点的父节点对应的该结点的指针指向它的右孩子即可。为什么这样可以呢,其实就是源于它的特性,我们删除的结点和父节点的关系无非就是左右关系,如果时左关系的话那么左子树的所有结点都要小于该结点的父节点,右关系同理,所以这种处理方案完全没有问题。
- 第三种,要删除的有孩子为空。和第二种情况一样,我们只需要将该结点的父节点对应该结点的指针指向它的左孩子即可。
- 第四种,要删除的左右孩子都不为空。这种情况比较复杂,无法采用父节点替代法。我们通常采用这种方法,就是找该结点的左子树的最大结点R(最右节点)或者右子树的最小结点R(最左结点)。让这两个结点中的其中一个和目标结点进行值的替换,这样就转化成了删除R结点。这两个结点任意一个放到目标节点的位置都满足我们搜索二叉树的要求。
bool erase(const key_type& key)
{
// 首先找到要删除的目标结点
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else {
//// 1.左右结点都为空
//if (cur->_left == nullptr && cur->_right == nullptr)
//{
// if (parent->_key > cur->_key)
// {
// parent->_left = nullptr;
// }
// else {
// parent->_right = nullptr;
// }
// delete cur;
//}
//// 2.左结点为空,右结点不为空
//else if (cur->_left == nullptr && cur->_right)
//{
// if (parent->_key > cur->_key)
// {
// parent->_left = cur->_right;
// }
// else {
// parent->_right = cur->_right;
// }
// delete cur;
//}
//// 3.右结点为空,左结点不为空
//else if (cur->_left && cur->_right == nullptr)
//{
// if (parent->_key > cur->_key)
// {
// parent->_left = cur->_left;
// }
// else {
// parent->_right = cur->_left;
// }
// delete cur;
//}
//1, 2, 3这三种情况可以归为一类就是左为空或者右为空
if (cur->_right == nullptr)
{
if (parent->_key > cur->_key)
{
parent->_left = cur->_left;
}
else {
parent->_right = cur->_left;
}
delete cur;
}
else if (cur->_left == nullptr)
{
if (parent != nullptr)
{
if (parent->_key > cur->_key)
{
parent->_left = cur->_right;
}
else {
parent->_right = cur->_right;
}
delete cur;
}
}
// 4.左右结点都不为空
else {
// 找左子树的最大结点
// 这里的Maxparent必须为cur因为可能进不去循环
Node* Maxparent = cur;
Node* Maxcur = cur->_left;
while (Maxcur->_right)
{
Maxparent = Maxcur;
Maxcur = Maxcur->_right;
}
swap(Maxcur->_key, cur->_key);
// 依旧是因为可能进不去循环所以要进行一下判断
if (Maxparent->_left == Maxcur)
{
Maxparent->_left = Maxcur->_left;
}
else
{
Maxparent->_right = Maxcur->_left;
}
delete Maxcur;
}
return true;
}
}
return false;
}
四、key和key/value模拟实现
key场景的模拟实现
namespace Code01_Journey
{
template<class K>
struct BSTreeNode {
typedef K key_type;
BSTreeNode(key_type key)
:_key(key)
, _left(nullptr)
, _right(nullptr)
{
}
BSTreeNode* _left;
BSTreeNode* _right;
key_type _key;
};
template<class K>
class BSTree {
typedef K key_type;
typedef BSTreeNode<key_type> Node;
public:
BSTree() = default;
// 采用封装递归函数的方式实现
~BSTree()
{
Destory(_root);
}
// 采用封装递归函数的方式实现
BSTree(const BSTree<key_type>& t)
{
_root = Copy(t._root);
// iocpy(t._root);
}
// t1 = t3
BSTree<key_type>& operator=(BSTree<key_type> t)
{
swap(_root, t._root);
return *this;
}
bool insert(key_type key)
{
// 1.树为空
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
else {
// 2.树不为空
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key == key)
{
return false;
}
else {
parent = cur;
cur = cur->_right;
}
}
cur = new Node(key);
if (parent->_key > key)
{
parent->_left = cur;
}
else {
parent->_right = cur;
}
return true;
}
}
bool find(const key_type& key)
{
if (_root == nullptr)
{
return false;
}
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
cur = cur->_left;
}
else if (cur->_key == key)
{
return true;
}
else {
cur = cur->_right;
}
}
return false;
}
bool erase(const key_type& key)
{
// 首先找到要删除的目标结点
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else {
//// 1.左右结点都为空
//if (cur->_left == nullptr && cur->_right == nullptr)
//{
// if (parent->_key > cur->_key)
// {
// parent->_left = nullptr;
// }
// else {
// parent->_right = nullptr;
// }
// delete cur;
//}
//// 2.左结点为空,右结点不为空
//else if (cur->_left == nullptr && cur->_right)
//{
// if (parent->_key > cur->_key)
// {
// parent->_left = cur->_right;
// }
// else {
// parent->_right = cur->_right;
// }
// delete cur;
//}
//// 3.右结点为空,左结点不为空
//else if (cur->_left && cur->_right == nullptr)
//{
// if (parent->_key > cur->_key)
// {
// parent->_left = cur->_left;
// }
// else {
// parent->_right = cur->_left;
// }
// delete cur;
//}
//1, 2, 3这三种情况可以归为一类就是左为空或者右为空
if (cur->_right == nullptr)
{
if (parent->_key > cur->_key)
{
parent->_left = cur->_left;
}
else {
parent->_right = cur->_left;
}
delete cur;
}
else if (cur->_left == nullptr)
{
if (parent != nullptr)
{
if (parent->_key > cur->_key)
{
parent->_left = cur->_right;
}
else {
parent->_right = cur->_right;
}
delete cur;
}
}
// 4.左右结点都不为空
else {
// 找左子树的最大结点
// 这里的Maxparent必须为cur因为可能进不去循环
Node* Maxparent = cur;
Node* Maxcur = cur->_left;
while (Maxcur->_right)
{
Maxparent = Maxcur;
Maxcur = Maxcur->_right;
}
swap(Maxcur->_key, cur->_key);
// 依旧是因为可能进不去循环所以要进行一下判断
if (Maxparent->_left == Maxcur)
{
Maxparent->_left = Maxcur->_left;
}
else
{
Maxparent->_right = Maxcur->_left;
}
delete Maxcur;
}
return true;
}
}
return false;
}
// 封装递归函数
void Inorder()
{
inorder(_root);
cout << endl;
}
private:
// 中序遍历
void inorder(Node* root)
{
if (root == nullptr)
{
return;
}
inorder(root->_left);
cout << root->_key << ' ';
inorder(root->_right);
}
// Destory
void Destory(Node* root)
{
if (root == nullptr)
{
return;
}
Destory(root->_left);
Destory(root->_right);
delete root;
}
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* copy = new Node(root->_key);
copy->_left = Copy(root->_left);
copy->_right = Copy(root->_right);
return copy;
}
void iocpy(Node* root)
{
if (root == nullptr)
return;
insert(root->_key);
iocpy(root->_left);
iocpy(root->_right);
}
private:
Node* _root = nullptr;
};
key/value场景的实现·
namespace Code02_Journey
{
template<class K, class T>
struct BSTreeNode {
typedef K key_type;
typedef T value_type;
BSTreeNode(const key_type& key, const value_type& value)
:_key(key)
,_value(value)
, _left(nullptr)
, _right(nullptr)
{}
BSTreeNode* _left;
BSTreeNode* _right;
key_type _key;
value_type _value;
};
template<class K, class T>
class BSTree {
typedef K key_type;
typedef T value_type;
typedef BSTreeNode<key_type, value_type> Node;
public:
BSTree() = default;
// 采用封装递归函数的方式实现
~BSTree()
{
Destory(_root);
}
// 采用封装递归函数的方式实现
BSTree(const BSTree<key_type, value_type>& t)
{
_root = Copy(t._root);
// iocpy(t._root);
}
// t1 = t3
BSTree<key_type, value_type>& operator=(BSTree<key_type, value_type> t)
{
swap(_root, t._root);
return *this;
}
bool insert(const key_type& key, const value_type& value)
{
// 1.树为空
if (_root == nullptr)
{
_root = new Node(key, value);
return true;
}
else {
// 2.树不为空
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key == key)
{
return false;
}
else {
parent = cur;
cur = cur->_right;
}
}
cur = new Node(key, value);
if (parent->_key > key)
{
parent->_left = cur;
}
else {
parent->_right = cur;
}
return true;
}
}
Node* find(const key_type& key)
{
if (_root == nullptr)
{
return nullptr;
}
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
cur = cur->_left;
}
else if (cur->_key == key)
{
return cur;
}
else {
cur = cur->_right;
}
}
return nullptr;
}
bool erase(const key_type& key)
{
// 首先找到要删除的目标结点
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else {
//// 1.左右结点都为空
//if (cur->_left == nullptr && cur->_right == nullptr)
//{
// if (parent->_key > cur->_key)
// {
// parent->_left = nullptr;
// }
// else {
// parent->_right = nullptr;
// }
// delete cur;
//}
//// 2.左结点为空,右结点不为空
//else if (cur->_left == nullptr && cur->_right)
//{
// if (parent->_key > cur->_key)
// {
// parent->_left = cur->_right;
// }
// else {
// parent->_right = cur->_right;
// }
// delete cur;
//}
//// 3.右结点为空,左结点不为空
//else if (cur->_left && cur->_right == nullptr)
//{
// if (parent->_key > cur->_key)
// {
// parent->_left = cur->_left;
// }
// else {
// parent->_right = cur->_left;
// }
// delete cur;
//}
//1, 2, 3这三种情况可以归为一类就是左为空或者右为空
if (cur->_right == nullptr)
{
if (parent->_key > cur->_key)
{
parent->_left = cur->_left;
}
else {
parent->_right = cur->_left;
}
delete cur;
}
else if (cur->_left == nullptr)
{
if (parent != nullptr)
{
if (parent->_key > cur->_key)
{
parent->_left = cur->_right;
}
else {
parent->_right = cur->_right;
}
delete cur;
}
}
// 4.左右结点都不为空
else {
// 找左子树的最大结点
// 这里的Maxparent必须为cur因为可能进不去循环
Node* Maxparent = cur;
Node* Maxcur = cur->_left;
while (Maxcur->_right)
{
Maxparent = Maxcur;
Maxcur = Maxcur->_right;
}
swap(Maxcur->_key, cur->_key);
swap(Maxcur->_value, cur->_value);
// 依旧是因为可能进不去循环所以要进行一下判断
if (Maxparent->_left == Maxcur)
{
Maxparent->_left = Maxcur->_left;
}
else
{
Maxparent->_right = Maxcur->_left;
}
delete Maxcur;
}
return true;
}
}
return false;
}
// 封装递归函数
void Inorder()
{
inorder(_root);
cout << endl;
}
private:
// 中序遍历
void inorder(Node* root)
{
if (root == nullptr)
{
return;
}
inorder(root->_left);
cout << root->_key << ' ' << root->_value << ' ';
inorder(root->_right);
}
// Destory
void Destory(Node* root)
{
if (root == nullptr)
{
return;
}
Destory(root->_left);
Destory(root->_right);
delete root;
}
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* copy = new Node(root->_key,root->_value);
copy->_left = Copy(root->_left);
copy->_right = Copy(root->_right);
return copy;
}
void iocpy(Node* root)
{
if (root == nullptr)
return;
insert(root->_key, root->_value);
iocpy(root->_left);
iocpy(root->_right);
}
private:
Node* _root = nullptr;
};
}
3996

被折叠的 条评论
为什么被折叠?



