一、 搜索二叉树概念
二叉搜索树又称二叉排序树,它或者是一棵空树;若它的左子树不为空,则左子树上所有节点的值都小于根节点的值;若它的右子树不为空,则右子树上所有节点的值都大于根节点的值;它的左右子树也分别为二叉搜索树。
二、搜索二叉树的操作
1、二叉搜索树的查找
2.、二叉搜索树的插入
a. 树为空,则直接插入
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
3、 二叉搜索树的删除
删除操作要求删除该值后,二叉树仍然要具备线索化。
首先查找元素是否在二叉搜索树中,如果不存在,则返回false, 否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
1:删除的结点是叶子或其子节点只有一个,这两种情况可以用同一套逻辑写
2:删除的结点有两个子节点,需要找左子树的最大或右子树的最小,然后将此结点的值更改,然后删除被用于替换的结点。
三、具体实现
1、首先定义树的结点的类和二插搜索树的类
template<class T>
struct BSTreeNode {
BSTreeNode(T key = T())
:left(nullptr)
,right(nullptr)
,_key(key)
{}
T _key;
BSTreeNode<T>* left;
BSTreeNode<T>* right;
};
这里写结点类的默认构造是便于后面new的操作,可以直接初始化。
template<class T>
class BSTree {
public:
typedef BSTreeNode<T> Node;
BSTree() = default;//强制生成默认构造
//...具体的一些操作
private:
Node* _root=nullptr;//内置类型给缺省值,给初始化列表默认为空指针
};
2、二叉搜索树的查找
根据搜索二叉树的性质,模拟查找这个过程即可
bool find(T key)
{
Node* pcur = _root;//遍历结点
while (pcur)
{
if (pcur->_key < key)
{
pcur = pcur->right;
}
else if (pcur->_key > key)
{
pcur = pcur->left;
}
else
return true;
}
return false;
}
3、二叉搜索树的插入
与查找的逻辑相似
bool insert(const T& key)//插入
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* pcur = _root;
Node* pre = nullptr;
while (pcur)//插入树里面不存在的值,pcur一定会走向空
{
pre = pcur;//插入位置的父节点
if (pcur->_key < key)
{
pcur = pcur->right;
}
else if (pcur->_key > key)
{
pcur = pcur->left;
}
else//插入的值已存在,无需插入
{
return false;
}
}
if (pre->_key < key)
pre->right = new Node(key);
else
pre->left = new Node(key);
return true;
}
4、二叉搜索树的删除
删除的思路:
a:删除的结点是叶子或其子节点只有一个,这两种情况可以用同一套逻辑写
b:删除的结点有两个子节点,需要找左子树的最大或右子树的最小,然后将此结点的值更改,然后删除被用于替换的结点。
1、大体的删除逻辑
bool erase(const T& key)
{
assert(_root);//断言空树报错
Node* pcur = _root;
Node* parent = nullptr;//删除节点的父结点
while (pcur)//删除结点为pcur
{
if (pcur->_key < key)
{
parent = pcur;
pcur = pcur->right;
}
else if (pcur->_key > key)
{
parent = pcur;
pcur = pcur->left;
}
else//找到了
{
//具体删除逻辑
}
}
}
2、删除的细节
if (pcur->left == nullptr)//左节点为空,右结点可能不为空
{}
else if (pcur->right == nullptr)//右节点为空,左结点可能不为空
{}
else//删除的结点有两个子节点,需要找左子树的最大或右子树的最小,然后将此结点的值更改
{}
1、左节点为空,右结点可能不为空(适用于左右都为空的结点情况)
if (pcur->left == nullptr)//左节点为空,右结点可能不为空
{
//考虑删到根结点的情况
if (pcur == _root)
{
_root = pcur->right;
delete pcur;
return true;
}
if (parent->left == pcur)//判断pcur位于父节点的左子树还是右子树
{
parent->left = pcur->right;
delete pcur;
return true;
}
else if (parent->right == pcur)
{
parent->right = pcur->right;
delete pcur;
return true;
}
}
2、右节点为空,左结点可能不为空
else if (pcur->right == nullptr)
{
//考虑删到根结点的情况
if (pcur == _root)
{
_root = pcur->left;
delete pcur;
return true;
}
if (parent->left == pcur)//判断pcur位于父节点的左子树还是右子树
{
parent->left = pcur->left;
delete pcur;
return true;
}
else if (parent->right == pcur)
{
parent->right = pcur->left;
delete pcur;
return true;
}
}
上面两种情况中都包含了一个特殊情况,就是删除的结点是根,且根结点的左右结点至少有一个为空。如果不单独处理,则会触发对空指针的引用问题。
3、删除的结点有两个子节点;需要找左子树的最大或右子树的最小,然后将此结点的值更改
这里的思路是找左子树的最大值
else//找左子树的最大值
{
Node* pleft = pcur->left;//删除结点左子树的根
Node* maxleft = pleft->right;//寻找左子树的最大值
if (maxleft == nullptr)//左子树的最大值就是根(左子树的根无右子树)
{
pcur->_key = pleft->_key;
pcur->left = pleft->left;
delete pleft;
return true;
}
else//左子树的根有右子树
{
Node* pparent = nullptr;//最大结点的父结点
while (maxleft->right)//maxleft->right为空时,maxleft就是左子树的最大值
{
pparent = maxleft;
maxleft = maxleft->right;
}
pcur->_key = maxleft->_key;
pparent->right = maxleft->left;
delete maxleft;
return true;
}
}
3、删除的整个过程
bool erase(const T& key)
{
assert(_root);//断言空树报错
Node* pcur = _root;
Node* parent = nullptr;//删除节点的父结点
while (pcur)//删除结点为pcur
{
if (pcur->_key < key)
{
parent = pcur;
pcur = pcur->right;
}
else if (pcur->_key > key)
{
parent = pcur;
pcur = pcur->left;
}
else//找到了
{
//删除操作:1、删除的结点是叶子或其子节点只有一个,这两种情况可以用同一套逻辑写
if (pcur->left == nullptr)//左节点为空,右结点可能不为空
{
//考虑删到根结点的情况
if (pcur == _root)
{
_root = pcur->right;
delete pcur;
return true;
}
if (parent->left == pcur)//判断pcur位于父节点的左子树还是右子树
{
parent->left = pcur->right;
delete pcur;
return true;
}
else if (parent->right == pcur)
{
parent->right = pcur->right;
delete pcur;
return true;
}
}
else if (pcur->right == nullptr)
{
//考虑删到根结点的情况
if (pcur == _root)
{
_root = pcur->left;
delete pcur;
return true;
}
if (parent->left == pcur)//判断pcur位于父节点的左子树还是右子树
{
parent->left = pcur->left;
delete pcur;
return true;
}
else if (parent->right == pcur)
{
parent->right = pcur->left;
delete pcur;
return true;
}
}
//2、删除的结点有两个子节点,需要找左子树的最大或右子树的最小,然后将此结点的值更改
else//找左子树的最大值
{
Node* pleft = pcur->left;//删除结点左子树的根
Node* maxleft = pleft->right;//寻找左子树的最大值
if (maxleft == nullptr)//左子树的最大值就是根(左子树的根无右子树)
{
pcur->_key = pleft->_key;
pcur->left = pleft->left;
delete pleft;
return true;
}
else//左子树的根有右子树
{
Node* pparent = nullptr;//最大结点的父结点
while (maxleft->right)//maxleft->right为空时,maxleft就是左子树的最大值
{
pparent = maxleft;
maxleft = maxleft->right;
}
pcur->_key = maxleft->_key;
pparent->right = maxleft->left;
delete maxleft;
return true;
}
}
}
}
return false;//没有找到删除的结点
}
四、对于搜索树的完善
1、对于一个类来说,它这种情况下需要显示的写析构,因为编译器默认生成的析构对内置类型不处理所以这里要对树中的每一个结点逐一释放空间
2、为了便于类的操作,这里也可以写一下它的拷贝构造(这里需要深拷贝)
3、二叉树的中序遍历就是排序,所以为了便于观察调试,也写一下它的中序遍历
template<class T>
class BSTree {
public:
typedef BSTreeNode<T> Node;
BSTree() = default;//强制生成默认构造
BSTree(const BSTree& node)//拷贝构造
{
//一个结点一个结点的拷贝(深拷贝),回调Copy()函数
_root = Copy(node._root);
}
~BSTree()//析构不能传参,所以要回调destroy
{
Destroy(_root);
_root = nullptr;
}
BSTree operator=(const BSTree& t)
{
Destroy(_root);//释放原有空间,防止内存泄漏
_root = Copy(t._root);
return *this;
}
//简便写法
/*BSTree operator=(BSTree<T> t)//传参生成深拷贝
{
std::swap(_root, t._root);
return *this;
}*/
private:
Node* Copy(Node* root)//采取先序遍历
{
if (root == nullptr)
return nullptr;
else
{
Node* temp = new Node(root->_key);
temp->left = Copy(root->left);
temp->right= Copy(root->right);
return temp;
}
}
void Destroy(Node* root)//逐一销毁结点
{//后续删除
if (root == nullptr)
return;
Destroy(root->left);
Destroy(root->right);
delete root;
}
void _InOrder(Node* root)//中序遍历
{
if (root == nullptr)
return;
_InOrder(root->left);
cout << root->_key << " ";
_InOrder(root->right);
}
Node* _root=nullptr;//内置类型给缺省值,给初始化列表默认为空指针
};
五、完整代码+测试
完整代码:
#include<iostream>
#include<assert.h>
using namespace std;
template<class T>
struct BSTreeNode {
BSTreeNode(T key = T())
:left(nullptr)
,right(nullptr)
,_key(key)
{}
T _key;
BSTreeNode<T>* left;
BSTreeNode<T>* right;
};
template<class T>
class BSTree {
public:
typedef BSTreeNode<T> Node;
BSTree() = default;//强制生成默认构造
BSTree(const BSTree& node)//拷贝构造
{
//一个结点一个结点的拷贝(深拷贝),回调Copy()函数
_root = Copy(node._root);
}
~BSTree()//析构不能传参,所以要回调destroy
{
Destroy(_root);
_root = nullptr;
}
BSTree operator=(const BSTree& t)
{
Destroy(_root);//释放原有空间,防止内存泄漏
_root = Copy(t._root);
return *this;
}
bool find(T key)
{
Node* pcur = _root;
while (pcur)
{
if (pcur->_key < key)
{
pcur = pcur->right;
}
else if (pcur->_key > key)
{
pcur = pcur->left;
}
else
return true;
}
return false;
}
bool insert(const T& key)//插入
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* pcur = _root;
Node* pre = nullptr;
while (pcur)//插入树里面不存在的值,pcur一定会走向空
{
pre = pcur;//插入位置的父节点
if (pcur->_key < key)
{
pcur = pcur->right;
}
else if (pcur->_key > key)
{
pcur = pcur->left;
}
else//插入的值已存在,无需插入
{
return false;
}
}
if (pre->_key < key)
pre->right = new Node(key);
else
pre->left = new Node(key);
return true;
}
bool erase(const T& key)
{
assert(_root);//断言空树报错
Node* pcur = _root;
Node* parent = nullptr;//删除节点的父结点
while (pcur)//删除结点为pcur
{
if (pcur->_key < key)
{
parent = pcur;
pcur = pcur->right;
}
else if (pcur->_key > key)
{
parent = pcur;
pcur = pcur->left;
}
else//找到了
{
//删除操作:1、删除的结点是叶子或其子节点只有一个,这两种情况可以用同一套逻辑写
if (pcur->left == nullptr)//左节点为空,右结点可能不为空
{
//考虑删到根结点的情况
if (pcur == _root)
{
_root = pcur->right;
delete pcur;
return true;
}
if (parent->left == pcur)//判断pcur位于父节点的左子树还是右子树
{
parent->left = pcur->right;
delete pcur;
return true;
}
else if (parent->right == pcur)
{
parent->right = pcur->right;
delete pcur;
return true;
}
}
else if (pcur->right == nullptr)
{
//考虑删到根结点的情况
if (pcur == _root)
{
_root = pcur->left;
delete pcur;
return true;
}
if (parent->left == pcur)//判断pcur位于父节点的左子树还是右子树
{
parent->left = pcur->left;
delete pcur;
return true;
}
else if (parent->right == pcur)
{
parent->right = pcur->left;
delete pcur;
return true;
}
}
//2、删除的结点有两个子节点,需要找左子树的最大或右子树的最小,然后将此结点的值更改
else//找左子树的最大值
{
Node* pleft = pcur->left;//删除结点左子树的根
Node* maxleft = pleft->right;//寻找左子树的最大值
if (maxleft == nullptr)//左子树的最大值就是根(左子树的根无右子树)
{
pcur->_key = pleft->_key;
pcur->left = pleft->left;
delete pleft;
return true;
}
else//左子树的根有右子树
{
Node* pparent = nullptr;//最大结点的父结点
while (maxleft->right)//maxleft->right为空时,maxleft就是左子树的最大值
{
pparent = maxleft;
maxleft = maxleft->right;
}
pcur->_key = maxleft->_key;
pparent->right = maxleft->left;
delete maxleft;
return true;
}
}
}
}
return false;//没有找到删除的结点
}
//Node* GetNode()//因为根是私有,所以要提供获取根的函数
//{
// return this->_root;
//}
void InOrder()
{
_InOrder(_root);//成员函数可以访问私有
}
private:
Node* Copy(Node* root)//采取先序遍历
{
if (root == nullptr)
return nullptr;
else
{
Node* temp = new Node(root->_key);
temp->left = Copy(root->left);
temp->right= Copy(root->right);
return temp;
}
}
void Destroy(Node* root)//逐一销毁结点
{//后续删除
if (root == nullptr)
return;
Destroy(root->left);
Destroy(root->right);
delete root;
}
void _InOrder(Node* root)//中序遍历
{
if (root == nullptr)
return;
_InOrder(root->left);
cout << root->_key << " ";
_InOrder(root->right);
}
Node* _root=nullptr;//内置类型给初始化列表默认为空指针
};
测试代码:
void test1()
{
int arr[] = { 4,4,45,23,12,33,5,3 };
BSTree<int> s;
for (const auto& e : arr)
{
s.insert(e);
}
s.InOrder();
cout << endl;
for (const auto& e : arr)
{
s.erase(e);
s.InOrder();
cout << endl;
}
}
void test2()
{
int arr[] = { 4,4,45,23,12,33,5,3 };
BSTree<int> s1;
for (const auto& e : arr)
{
s1.insert(e);
}
s1.InOrder();
cout << endl;
BSTree<int> s2(s1);
s2.erase(4);
s2.InOrder();
cout << endl;
s1 = s2;
s1.InOrder();
}