一、概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
1)若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值。
2)若它的右子树不为空,则右子树上所有结点的值都大于等于根结点的值。
3)它的左右子树也分别为二叉搜索树。
4)二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义,后续我们学习map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等值,multimap/multiset支持插入相等值。
二、性能分析
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其高度为:log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其高度为:N
所以综合而言二叉搜索树增删查改时间复杂度为:O(N),那么这样的效率显然是无法满足我们需求的,后面博客中的平衡二叉搜索树AVL树和红黑树,才能适用于我们在内存中存储和搜索数据。另外需要说明的是,二分查找也可以实现O(log2N)级别的查找效率,但是二分查找有两大缺陷:
1.需要存储在支持下标随机访问的结构中,并且有序。
2.插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据一般需要挪动数据。这里也就体现出了平衡二叉搜索树的价值。
三、搜索二叉树的底层思想和模拟实现
1)插入
插入的具体过程如下:
1、树为空,则直接新增结点,赋值给root指针
2、树不空,按二叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。
3、如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。(要注意的是要保持逻辑一致性,插入相等的值不要一会往右走,一会往左走)
代码示例:
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
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);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
注意:这里的代码只实现插入的过程,结点的定义和二叉搜索树的定义还没有实现,后面的完整代码会具体实现。
2)查找
流程:
1、从根开始比较,查找x,x比根的值大则往右边走查找,x比根值小则往左边走查找。
2、最多查找高度次,走到到空,还没找到,这个值不存在。
3、如果不支持插入相等的值,找到x即可返回4;如果支持插入相等的值,意味着有多个x存在,一般要求查找中序的第一个x。
代码示例:
bool Find(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return true;
}
}
return false;
}
3)删除
首先查找元素是否在二叉搜索树中,如果不存在,则返回false。如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)
1、要删除结点N左右孩子均为空。
2、要删除的结点N左孩子位空,右孩子结点不为空。
3、要删除的结点N右孩子位空,左孩子结点不为空。
4、要删除的结点N左右孩子结点均不为空。
解决方案:
1、把N结点的父亲对应孩子指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是一样的)。
2、把N结点的父亲对应孩子指针指向N的右孩子,直接删除N结点。
3、把N结点的父亲对应孩子指针指向N的左孩子,直接删除N结点。
4、无法直接删除N结点,因为N的两个孩子无处安放,只能用替换法删除。找N左子树的值最大结点R(最右结点)或者N右子树的值最小结点R(最左结点)替代N,因为这两个结点中任意一个,放到N的位置,都满足二叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转而变成删除R结点,R结点符合情况2或情况3,可以直接删除。
思想风暴:
代码示例:
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
if (cur->_left==nullptr)//
{
if (cur == _root)//判断
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)//当前结点在父亲的左边
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)//判断
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
else//俩个孩子都不为空
{
Node* pMinRight = cur;
Node* minRight = cur->_right;
while (minRight->_left)
{
pMinRight = minRight;
minRight = minRight->_left;
}
swap(cur->_key,minRight->_key);
if (pMinRight->_left == minRight)
{
pMinRight->_left = minRight->_right;
}
else
{
pMinRight->_right = minRight->_right;
}
delete minRight;
}
return true;
}
}
return false;
}
补充知识:如果我们显示写了构造函数,就不会有默认构造,C++11新增了一个关键字default,这个关键字可以在强制生成默认构造。
代码示例:
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree() = default;//强制生成默认构造
BSTree(const BSTree<K>& t)
{
_root = Copy(t._root);
}
Node* _root = nullptr;
};
类模板在外面要带参数,在类里面可以不带参数,相当于是一种特殊处理:
BSTree(const BSTree<K>& t)//也可以这么写 BSTree(const BSTree& t)
{
_root = Copy(t._root);
}
四、二叉搜索树的使用场景
1、key搜索场景
小区判断车是否本小区的居民的车:搜索车的车牌是否是本小区的。
检查单词是否输入正确:用户输入单词,根据单词的拼写搜索正确的单词,并且和用户写的单词进行匹配。
2、key和value的搜索场景
小区收停车费:把车的停车的时间和车牌记录下来,等车走的时候根据车牌搜索车停车时间进行收费。
中英互译:根据用户输入的中文或者英文找对对应的英文或者中文。
五、完整代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
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:
BSTree() = default;//强制生成默认构造
BSTree(const BSTree<K>& t)//也可以这么写 BSTree(const BSTree& t)
{
_root = Copy(t._root);
}
~BSTree()
{
Destory(_root);
_root = nullptr;
cout << "~BSTree()" << endl;
}
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
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);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool Find(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return true;
}
}
return false;
}
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
if (cur->_left==nullptr)//
{
if (cur == _root)//判断
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)//当前结点在父亲的左边
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)//判断
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
else//俩个孩子都不为空
{
Node* pMinRight = cur;
Node* minRight = cur->_right;
while (minRight->_left)
{
pMinRight = minRight;
minRight = minRight->_left;
}
swap(cur->_key,minRight->_key);
if (pMinRight->_left == minRight)
{
pMinRight->_left = minRight->_right;
}
else
{
pMinRight->_right = minRight->_right;
}
delete minRight;
}
return true;
}
}
return false;
}
private:
void Destory(Node* root)
{
if (root == nullptr)
{
return;
}
Destory(root->_left);
Destory(root->_right);
delete root;
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
Node* Copy(Node* root)
{
if (root == nullptr)
{
return nullptr;
}
Node* copy = new Node(root->_key);
copy->_left = Copy(root->_left);
copy->_left = Copy(root->_right);
return copy;
}
Node* _root = nullptr;
};
完!!