目录
二叉搜索树的概念
⼆叉搜索树的性能分析
⼆叉搜索树的插⼊
⼆叉搜索树的查找
⼆叉搜索树的删除
中序遍历结果为升序序列
二叉搜索树的概念
⼆叉搜索树⼜称⼆叉排序树,它或者是⼀棵空树,或者是具有以下性质的⼆叉树
• 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值• 若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值• 它的左右⼦树也分别为⼆叉搜索树• ⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义,后续我 们学习map/set/multimap/multiset系列容器底层就是⼆叉搜索树,其中map/set不⽀持插⼊相等 值,multimap/multiset⽀持插⼊相等值
⼆叉搜索树的性能分析
最优情况下,⼆叉搜索树为完全⼆叉树(或者接近完全⼆叉树),其⾼度为: log 2 N最差情况下,⼆叉搜索树退化为单⽀树(或者类似单⽀),其⾼度为: N所以综合⽽⾔⼆叉搜索树增删查改时间复杂度为: O ( N )中序遍历二叉搜索树得到一个升序序列
⼆分查找也可以实现 O (log 2 N ) 级别的查找效率,但是⼆分查找有两⼤缺陷:1. 需要存储在⽀持下标随机访问的结构中,并且有序。 (有序数组)2. 插⼊和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数 据。
⼆叉搜索树的插⼊
1. 树为空,则直接新增结点,赋值给root指针2. 树不空,按⼆叉搜索树性质,插⼊值⽐当前结点⼤往右⾛,插⼊值⽐当前结点⼩往左⾛,找到空位 置,插⼊新结点。3. 如果⽀持插⼊相等的值,插⼊值跟当前结点相等的值可以往右⾛,也可以往左⾛,找到空位置,插 ⼊新结点。(要注意的是要保持逻辑⼀致性,插⼊相等的值不要⼀会往右⾛,⼀会往左⾛)
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key); // 1、若根为空,则就插入在此处
return true;
}
Node* parent = nullptr; // 使用parent保存父节点,用于后续链接
Node* cur = _root;
while (cur)
{
if (cur->_key < key) // 2、不断递归寻找合适插入位置
要满足左小右大的特点
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
cout << "所要插入的值已经存在" << endl;
return false;
}
}
cur = new Node(key);
if (parent->_key > key) // 3、找到之后使用parent和cur进行新建结点的接入
parent->_left = cur;
else
parent->_right = cur;
return true;
}
⼆叉搜索树的查找
如果⽀持插⼊相等的值,意味着有多个x存在,⼀般要求查找中序的第⼀个x,如下图,查找3,要
找到1的右孩⼦的那个3返回。
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key == key)
return true;
else if (cur->_key > key)
cur = cur->_left;
else
cur = cur->_right;
}
return false;
}
⼆叉搜索树的删除
父要接管删除节点的剩余结点
若cur的左为空,那么父节点要处理cur的右子树
若cur的右为空,那么父节点要处理cur的左子树
若cur的左右均不为空,那么就找cur右子树的最左结点进行接管,最后处理最左节点的右子树
三种情况,最终处理时又都有俩种 孩子在左、在右的情况。
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur) // 遍历搜索树,寻找需要删除的结点位置
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
{
// 此时找到了需要删除的结点位置,进行删除操作
// 我的左为空,父亲处理我的右
if (cur->_left == nullptr)
{
if (cur == _root) // 左为空,并且cur为根
_root = cur->_right;
if (cur == parent->_right)
parent->_right = cur->_right;
else if (cur == parent->_left)
parent->_left = cur->_right;
delete cur;
}
// 我的右为空,父亲处理我的左
else if (cur->_right == nullptr)
{
if (cur == _root)// 右为空,并且cur为根
_root = cur->_left;
if (cur == parent->_right)
parent->_right = cur->_left;
else
parent->_left = cur->_left;
delete cur;
}
else
{
// 左右均不为空,需要找右子树的最左(最小)结点来接管我的位置
Node* minRightNode = cur->_right; //通过minRightNode 和 minRightParent
Node* minRightParent = cur; //找右子树最左节点
while (minRightNode->_left)
{
minRightParent = minRightNode;
minRightNode = minRightNode->_left; // 一直向最左移动,注意保存父节点位置
}
cur->_key = minRightNode->_key; // 将结点的_key值进行替换即可
if (minRightParent->_right != minRightNode) // 处理minRightNode结点,只有俩种情况,一种minRightNode是minRightParent的左孩子、另一种是右孩子
minRightParent->_left = minRightNode->_right; // 如果minRightNode是左孩子,那么minRightParent要接管它的右子树
else
minRightParent->_right = minRightNode->_right; // 如果minRightNode是右孩子,那么minRightParent要接管它的左子树
delete minRightNode;
}
return true;
}
}
return false;
}
中序遍历结果为升序序列
最开始没有写 InOrder() 函数,在main中进行调用时发生了 t.InOrder(??) 无法给左式括号内传参的问题,所以又加入了一个InOrder 和 _InOrder()俩个递归函数嵌套的形式。
template<class K>
class BSTree
{
typedef BSTNode<K> Node;
public:
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
Node* _root = nullptr;
};