二叉搜索树
二叉搜索树也叫二叉排序树,它可以是一棵空树。它具有一下特点:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
- 没有键值(key)相等的节点
简单的说就是左边的值都比该节点的值小,右边的值都比该节点的值大。
像这样:
二叉搜索树的查找
因为二叉搜索树的特点就是左边的值都比该节点的值小,右边的值都比该节点的值大,所以我们在进行查找的时候就通过key值比较,如果要查找的值比该节点的值小则去左子树查找,如果比该节点的值大则去右子树查找。
如上图,查找0,我们发现比5小,于是去5的左边找,比3小,于是去3的左边找,比1小,于是去1的左边找,然后找到了~
查找6,我们发现比5大,于是去5的右边找,比7小,于是去7的左边找,然后找到了~
代码实现:
Node* Find(const K &k)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first > k)
{
cur = cur->_left;
}
else if (cur->_kv.first < k)
{
cur = cur->_right;
}
else
return cur;
}
return nullptr;
}
二叉搜索树的插入
因为二叉搜索树的性质,所以插入会有两种情况:
1.如果树为空,则直接插入
2.如果树不为空,那么需要找到对应的位置在进行插入,找位置的时候也要根据比该节点小去左边,比该节点大去右边的原则
如图,插入10,因为10比5大,所以去5的右子树,比7大,去7的右子树,比8大,去8的右子树,比9大,去9的右子树,9的右子树为空所以就插入这个新节点10
代码如下:
bool Insert(const std::pair<K, V> &kv)//插入
{
//空树情况
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
//不为空树的情况
Node* cur = _root;
Node* parent = nullptr;
//找到要插入的位置cur
while (cur)
{
if (cur->_kv.first > kv.first)//插入的值比它小去左边
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < kv.first)//插入的值比它大去右边
{
parent = cur;
cur = cur->_right;
}
else//相等返回false
return false;
}
//进行连接节点
cur = new Node(kv);
if (parent->_kv.first > cur->_kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
return true;
}
二叉搜索树的删除
删除一个节点有以下几种情况:
1.该节点的左子树为空,那么让该节点的父节点指向它的右子树
如下图,要删除的是8,那么它的左子树为空,就让它的父节点7指向它的右子树9。
另外还有一种特殊情况,如果左子树为空时,删除的正好是根节点,那么直接让根节点等于根节点的右子树。
如下图,删除的是5,5正好是根节点,因此直接让根节点指向它的右子树7。
代码如下:
if (cur->_left == nullptr)
{
if (parent == nullptr)//删的是根节点
{
_root = cur->_right;//根等于根的右子树
}
else//删除的不是根节点,就让它的父节点指向它的右子树
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
}
2.该节点的右子树为空,那么让该节点的父节点指向它的左子树
如下图,要删除的是1,1的右子树为空,那么就让1的父节点3指向它的左子树0。
这里也有一种特殊情况,如果右子树为空时,删除的正好是根节点,那么直接让根节点等于根节点的左子树。
如下图,要删除的是5,正好是根节点,那么直接让根节点指向它的左子树3。
代码如下:
//接上情况1的代码
else if (cur->_right == nullptr)//要删除的是根节点
{
if (parent == nullptr)
{
_root = cur->_left;
}
else//要删除的不是根节点,父节点指向它的左子树
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
}
3.该节点的左右子树都不为空,那么就要找一个节点来代替它
这个代替的节点也是有规律的,我们可以找右树的最左节点(左子树为空的节点),也可以找左树的最右节点(右子树为空的节点)。
方法1:找一个右树的最左节点来代替它
//方法1:找一个右树的最左节点来代替它
//右树的最左节点就是左为空的那个节点
Node* preplace = cur;
Node* replace = cur->_right;
while (replace->_left)
{
preplace = replace;
replace = replace->_left;
}
cur->_kv = replace->_kv;
del = replace;
//删除代替的最左节点(父亲指向它的右)
if (preplace->_left == replace)
{
preplace->_left = replace->_right;
}
else
{
preplace->_right = replace->_right;
}
方法2:找一个左树的最右节点来代替它
//方法2:找一个左树的最右节点来代替它
Node* preplace = cur;
Node* replace = cur->_left;
while (replace->_right)
{
preplace = replace;
replace = replace->_right;
}
cur->_kv = replace->_kv;
del = replace;
if (preplace->_left == replace)
{
preplace->_left = replace->_left;
}
else
{
preplace->_right = replace->_left;
}
二叉搜索树的遍历
采用中序遍历就能够按序打印这个二叉搜索树。
这里采用封装一层的方法,这样每次调用这个函数就不需要传参了。
void Inorder()//中序遍历
{
_Inorder(_root);
cout << endl;
}
void _Inorder(Node* root)
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << " ";
_Inorder(root->_right);
}