二叉搜索树的概念
二叉搜索树也可以称为二叉排序树,它也可以是一个空树
- 若它的左子树不为空,则左子树上所有节点的值都小于****根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
->那么根据这个理论知识我们就可以判断哪个是搜索二叉树了!XD
二叉搜索树的操作
我们需要认识并且熟悉掌握各个二叉搜索树的操作,以便更好的用代码实现。
接下来下面的树就是常规的二叉搜索树啦!
->用数组表示是这样子的👇
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
二叉搜索树的中序遍历是有序的,不妨你来试一试
- 二叉树的查找
a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
b、最多查找高度次,走到到空,还没找到,这个值不存在。
趁这个知识点还热乎着,我们赶紧用代码实现一下吧~
标注:这里只是伪代码,在后面模拟实现树时会将完整的代码贴出,这里只是为了更好的理解
bool Find(const K& key) //查找值
{
Node* cur = _root; //定义一个 cur 用于遍历树
while (cur) //当cur为空的时候跳出循环
{
if (cur->_key < key) //当查找值大于节点值
{
cur = cur->_right; //往右走
}
else if (cur->_key > key)//当查找值小于节点值
{
cur = cur->_left; //往左走
}
else
{
//当相等的时候,说明已经找到了
return true;
}
}
return false;
}
- 二叉树的插入
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
但要在实现代码这方面还需要多一个步骤!
因为当我们找到可以插入的位置时,只是给节点赋值,而没有真正的与二叉树进行连接,所以我们需要记录每一次cur 的父节点
bool Insert(const K& key)
{
//前提条件:若空树,则该插入的节点就成为根
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) //当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); //new一个新的节点
//判断新插入的节点与父节点值的大小来决定是插入左子树还是右子树
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
- 🔺二叉搜索树的删除🔺
首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
- 其中,比较简单的就是要删除的节点只有一个孩子,那么就可以直接有父节点指向其孩子节点
- 但是,当我们要删除的节点有两个孩子的时候,就需要平衡因子来达到搜索二叉树的稳定。
(就比如说当我们想要删除3的时候,父节点是无法直接继承子节点)
解决办法:找一个人来替代
1.左子树的最大
2.右子树的最小
两种方法都可以在这里我们用第二种方法来实现
完整二叉搜索树代码
代码已经过测试,可安全使用
//#pragma once
/*
* 在实现树的时候要分两部分,首先树是由多个节点构成的
* 而每个节点内部也分为:值,指向左子树的指针,指向右子树的指针
* 所以我们要先包装节点,然后再来实现树
*/
//定义树节点
template<class K>
//由于节点存储的值类型不固定(int,double),
//所以用模板以 K 当作一种类型来定义
struct BSTNode
{
K _key;//定义值
BSTNode<K>* _left; //左孩子
BSTNode<K>* _right;//右孩子
//构造函数
BSTNode(const K& key)
:_key(key)
, _left(nullptr)
, _right(nullptr)
{}
};
template<class K>
class BSTree
{
typedef BSTNode<K> Node; //为了代码简洁性,再次重定义为Node
public:
BSTree() : _root(nullptr) {}
bool Insert(const K& key)
{
//前提条件:若空树,则该插入的节点就成为根
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) //当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); //new一个新的节点
//判断新插入的节点与父节点值的大小来决定是插入左子树还是右子树
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
bool Find(const K& key) //查找值
{
Node* cur = _root; //定义一个 cur 用于遍历树
while (cur) //当cur为空的时候跳出循环
{
if (cur->_key < key) //当查找值大于节点值
{
cur = cur->_right; //往右走
}
else if (cur->_key > key)//当查找值小于节点值
{
cur = cur->_left; //往左走
}
else
{
//当相等的时候,说明已经找到了
return true;
}
}
return false;
}
//删除
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
{
//删除
/*
* 1.若被删除节点只有一个孩子,那么就要判断:
* 节点为父节点的 左/右 子树
* 节点的一个孩子是 左孩子 还是 有孩子
*
* 举例子:若被删除的节点是父节点的 右 子树,且被删除节点只有一个 左 子树
* 那么,parent->_right = cur->_left
*
* 2.若被删除节点是两个孩子的情况
* 右子树的最小节点作为替代节点(因为右子树的最小比左子树大,且均小于右子树)
*
* 如果父亲为空(即删除的是根节点,那么需要改变_root)
*/
if (cur->_left == nullptr)//如果我的左是空的,那么指向我的右
{ //如果没有父亲就改_root
if (parent == nullptr)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
{
//如果我是父亲的左子树,那么父亲的左指向我的孩子
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
//如果没有父亲就改_root
if (parent == nullptr)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
return true;
}
else
{
//2个孩子的情况
Node* rightMinP = cur; //rightMinParent
//设置parent的目的是,为了删除这个替代节点的原始位置
Node* rightMin = cur->_right;
//找出右子树的最左节点(即:最小节点)
while (rightMin->_left)
{
rightMinP = rightMin;
rightMin = rightMin->_left;
}
cur->_key = rightMin->_key;
//如果被代替的节点有孩子,那么就需要父母接管孩子
//判断被删节点是父节点的左子树还是右子树
if (rightMinP->_left == rightMin)
rightMinP->_left = rightMin->_right;
else
rightMinP->_right = rightMin->_right;
delete rightMin;
return true;
}
}
}
return false;
}
//中序遍历(为了解决私有root无法访问问题于是间接调用)
void InOrder()
{
_InOrder(_root);
}
private:
//子函数
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
Node* _root;//根
};
下面附赠测试代码:
#include<iostream>
using namespace std;
#include"SearchBinaryTree.h"
int main()
{
int a[] = { 8,3,1,10,6,4,7,14,13 };
BSTree<int> t;
for (auto e : a)
{
t.Insert(e);
}
for (auto e : a)
{
t.Erase(e);
t.InOrder();
cout << endl;
}
//中序遍历要传root,但是root是私有的
//所以我们可以嵌套一个函数使之可以调用内部的_root
return 0;
}
补充
如果插入的值是有序的,那么搜索二叉树就会成为类似于链表的形态,所以搜索二叉树的效率是没办法保证的
增删查改的时间复杂度为:O(N)
- 那么搜索二叉树的缺点衍生出了各种AVL树,红黑树等等,那么这些会在以后陆续讲解,本次博客只是为了后文 set 和 map 做出铺垫
–
如果你喜欢这篇文章,别忘了点赞
一个小小的赞都是对我最大的支持
感谢您看到这里❤