@TOC
在之前的博客中,我们有谈过二叉树的相关知识,对二叉树进行了一个基础的认识,而在今天的博客中,我们来将二叉树中一种特殊的树————二叉搜索树。
下面是之前代码的链接【精选】二叉树基础概念和堆的实现_疏 石 兰 兮的博客-CSDN博客
在之前的学习中,我们知道二叉树也是一种存储数据的容器,但是如果我们在使用二叉树查找数据的时候,不管是使用前序,中序,后序还是层序这些遍历方式去查找数据,都会显得十分麻烦,所以前辈们设计了一种查找数据十分方便的二叉树————二叉搜索树。
二叉搜索树又称为二叉排序树。它可以是一个空树,如果不为空,就必须具有以下性质的二叉树:
若它的左子树不为空,则左子树上面所有结点的值都小于根结点的值。
若它的右节点不为空,则右子树上面所有结点的值都大于根结点的值。
它的左右子树同样满足以上条件。
如图:
根据上面的特点,我们会发现,这棵树的最左边叶子结点一定是最小的树,它的根结点就是次小值,第三小的值就是根右节点的最左边叶子结点,依次类推,最大的结点自然就是最右边的节点。
而且如果使用中序遍历,我们就会发现打出来的序列就是[ 1, 3, 4, 6, 7, 8, 10,13, 14]正好是从小到大。
a. 从根开始比较,查找,比根大则往右边边走查找,比跟小则往左边走查找
b. 最多查找高度次,走到到空,还没找到,这个值不存在。
插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针。
b. 树不为空,按二叉树搜索树性质查找插入位置,插入新结点。
首先查找元素是否在二叉搜索树中,如果不存在,则返回,否则要删除的结点可能分下面四种情况:
a. 要删除结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左,右孩子结点
其中,a可以和b,c结合
首先 我们要设置我们的二叉树结点的结构体
template
struct BSTNode
{
BSTNode(const T& data = T())//是一个带有默认参数的构造函数,这里的T()调用了类型T的默认函数,用于创建一个默认值的数据对象。
:_Lchild(nullptr)
:_Rchild(nullptr)
:_data(data)
{}
//这个函数既可以当作默认构造函数,又可以当作拷贝构造函数
BSTNode* _Lchild;
BSTNode* _Rchild;
T _Data;
};
//插入函数迭代版
bool Insert(const T& data)
{
//如果这个树为空
if (_root == nullptr)
{
Node* newnode = new Node(data);
_root = newnode;
return true;
}
Node* cur = _root;
while (cur)
{
if (cur->_Data == data)
{
//树中已经含有该值
cout << "该值已存在" << endl;
return false;
}
else if (cur->_Data < data)
{
if (cur->_Rchild == nullptr)
{
//说明已经找到正确的位置了
Node* newnode = new Node(data);
cur->_Rchild = newnode;
return true;
}
cur = cur->_Rchild;
}
else if (cur->_Data > data)
{
if (cur->_Lchild == nullptr)
{
//说明已经找到正确的位置了
Node* newnode = new Node(data);
cur->_Lchild = newnode;
return true;
}
cur = cur->_Lchild;
}
}
return false;
}
bool _EraseR(Node*& root, const T& data, Node* parent)
{
if (root == nullptr)
{
cout << "树为空" << endl;
return false;
}
else if (root->_Data == data)
{
//找到之后就可以进行删除了
//删除该结点之后必须保持该树依然是一个二叉搜索树,而这个时候就得分情况
//这时候得分情况
//1.假如该节点左孩子为空
if (!root->_Lchild)
{
Node* node = root;
//如果root是根结点
if (root == _root)
{
root = root->_Rchild;
}
else
{
if (root == parent->_Lchild)
{
parent->_Lchild = root->_Rchild;
}
else
{
parent->_Rchild = root->_Rchild;
}
}
delete node;
node = nullptr;
}
//2. 假如cur的右子树为空
else if (!root->_Rchild)
{
Node* node = root;
if (root == _root)
{
root = root->_Lchild;
}
else
{
if (parent->_Lchild == root)
{
parent->_Lchild = root->_Lchild;
}
else
{
parent->_Rchild = root->_Lchild;
}
}
delete node;
node = nullptr;
}
//3.假如root的左右子树都不为空
else if (root->_Lchild && root->_Rchild)
{
//要找到cur右结点的最小结点
Node* rootRMin = root->_Rchild;
while (rootRMin->_Lchild)
//我们要找的是root右子树的最小值,就要让rootRMin一直朝左走,
//当rootRMin的左子树为空时,那么rootRMin就到了最小
{
rootRMin = rootRMin->_Lchild;
}
swap(rootRMin->_Data, root->_Data);
delete rootRMin;
rootRMin = nullptr;
}
return true;
}
else if (root->_Data > data)
{
return _EraseR(root->_Lchild, data, root);
}
else if (root->_Data < data)
{
return _EraseR(root->_Rchild, data, root);
}
return false;
}
//二叉树查找迭代版
bool Find(const T& data)
{
Node* cur = _root;
while (cur)
{
if (cur->_Data > data)
{
cur = cur->_Lchild;
}
else if (cur->_Data < data)
{
cur = cur->_Rchild;
}
else if (cur->_Data == data)
{
return true;
}
}
cout << "树中没有这个值" << endl;
return false;
}
//这里选择中序遍历
void traverse()
{
stack st;
if (_root == nullptr)
{
cout << "树为空" << endl;
return;
}
st.push(_root);
while (!st.empty())
{
Node* node = st.top();
st.pop();
if (node != nullptr)
{
if (node->_Rchild)
{
st.push(node->_Rchild);
}
st.push(node);
st.push(nullptr);
if (node->_Lchild)
{
st.push(node->_Lchild);
}
}
else
{
node = st.top();
st.pop();
cout << node->_Data << " ";
}
}
cout << endl;
}
我写迭代遍历的主体思路:区分父亲结点和孩子结点,父亲结点会在和孩子结点之间添加一个nullptr,用于区分。
//析构函数迭代版
~BSTree()
{
//使用后序遍历
//因为我们要删除左结点之后还要能找到右结点,所以得保留根节点
stack s;
Node* cur = _root;
if (cur == nullptr)
{
return;
}
s.push(cur);
while (!s.empty())
{
Node* node = s.top();
if (node != nullptr)
{
s.pop();
s.push(node);
s.push(nullptr);
if(node->_Rchild)
{
s.push(node->_Rchild);
}
if (node->_Lchild)
{
s.push(node->_Lchild);
}
}
else
{
s.pop();
node = s.top();
s.pop();
delete node;
}
}
}
析构函数在写迭代版本的时候,在释放左结点之后还需要释放右结点,所以必须使用后序遍历来进行释放。
二叉搜索树的代码难点就是在迭代,递归要比迭代简单很多,所以我把主要的函数进行递归化
//二叉搜索树的查找递归版
bool _FindR(Node* root, const T& data)
{
if (root == nullptr)
{
return false;
}
if (root->_Data == data)
{
return true;
}
else if (root->_Data > data)
{
return _FindR(root->_Lchild, data);
}
else if (root->_Data < data)
{
return _FindR(root->_Rchild, data);
}
return true;
}
bool _EraseR(Node*& root, const T& data, Node* parent)
{
if (root == nullptr)
{
cout << "树为空" << endl;
return false;
}
else if (root->_Data == data)
{
//找到之后就可以进行删除了
//删除该结点之后必须保持该树依然是一个二叉搜索树,而这个时候就得分情况
//这时候得分情况
//1.假如该节点左孩子为空
if (!root->_Lchild)
{
Node* node = root;
//如果root是根结点
if (root == _root)
{
root = root->_Rchild;
}
else
{
if (root == parent->_Lchild)
{
parent->_Lchild = root->_Rchild;
}
else
{
parent->_Rchild = root->_Rchild;
}
}
delete node;
node = nullptr;
}
//2. 假如cur的右子树为空
else if (!root->_Rchild)
{
Node* node = root;
if (root == _root)
{
root = root->_Lchild;
}
else
{
if (parent->_Lchild == root)
{
parent->_Lchild = root->_Lchild;
}
else
{
parent->_Rchild = root->_Lchild;
}
}
delete node;
node = nullptr;
}
//3.假如root的左右子树都不为空
else if (root->_Lchild && root->_Rchild)
{
//要找到cur右结点的最小结点
Node* rootRMin = root->_Rchild;
while (rootRMin->_Lchild)
//我们要找的是root右子树的最小值,就要让rootRMin一直朝左走,
//当rootRMin的左子树为空时,那么rootRMin就到了最小
{
rootRMin = rootRMin->_Lchild;
}
swap(rootRMin->_Data, root->_Data);
delete rootRMin;
rootRMin = nullptr;
}
return true;
}
else if (root->_Data > data)
{
return _EraseR(root->_Lchild, data, root);
}
else if (root->_Data < data)
{
return _EraseR(root->_Rchild, data, root);
}
return false;
}
//二叉搜索树的插入递归版
bool _InsertR(Node*& root, const T& data)
{
if (root == nullptr)
{
Node* newnode = new Node(data);
root = newnode;
return true;
}
else if (root->_Data == data)
{
return false;
}
else if (root->_Data > data)
{
if (root->_Lchild == nullptr)
{
Node* newnode = new Node(data);
root->_Lchild = newnode;
return true;
}
return _InsertR(root->_Lchild, data);
}
else if (root->_Data < data)
{
if (root->_Rchild == nullptr)
{
Node* newnode = new Node(data);
root->_Rchild = newnode;
return true;
}
return _InsertR(root->_Rchild, data);
}
return false;
}