之前在数据结构的学习中我们学习过,二叉树这个概念。在本文中我们将介绍一种二叉树的进阶版本二叉搜索树。
二叉搜索树概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
二叉搜索树操作
查找
- 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
- 最多查找高度次,走到到空,还没找到,这个值不存在。
// 非递归版本
bool Find(const K& key) {
Node* cur = _root; // 从根节点开始遍历二叉树
while (cur) {
if (cur->_key < key) { // 比根节点小往左边找
cur = cur->_right;
}
else if (cur->_key > key) { // 比根节点大往右树找
cur = cur->_left;
}
else {
return true;
}
}
return false;
}
// 递归版本
bool FindR(const K& key) { // 在递归的版本中我们需要编写两个函数因为我们需要传入头结点,而正常情况下头结点是私有的再类外无法访问到的,因此需要我们创建子函数进行处理。
return _FindR(_root, key);
}
bool _FindR(Node* root, const K& key)
{
if (root == nullptr) // 没有找到返回false
return false;
if (root->_key == key) // 找到了返回true
return true;
if (root->_key < key)
return _FindR(root->_left, key);
else
return _FindR(root->_right, key);
}
插入
- 插入的具体过程如下:
- 树为空,则直接新增节点,赋值给root指针
- 树不空,按二叉搜索树性质查找插入位置,插入新节点
在插入中同样有非递归的版本与递归的版本:
// 非递归版本
bool Insert(const K& key) {
if (_root == nullptr) { // 假如根节点的位置为空值就直接插入
_root = new Node(key);
return true;
}
Node* parent = nullptr;// 需要记录父节点的位置,将要插入的节点链接在父节点之后
Node* cur = _root;
while (cur) {
if (key > cur->_key) {
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key) {
parent = cur;
cur = cur->_left;
}
else {
return false;
}
}
cur = new Node(key);
if (key > parent->_key) {
parent->_right = cur;
}
else { // key < parent->key
parent->_left = cur;
}
return true;
}
// 递归版本
bool _InsertR(Node*& root, const K& key)
{
if (root == nullptr) { // 根节点为空就直接插入返回
root = new Node(key);
return true;
}
if (root->_key < key) { // 比根节点大在右树中进行递归查找
return _InsertR(root->_right, key);
}
else if (root->_key > key) { // 比根节点小在左树中进行查找
return _InsertR(root->_left, key);
}
else {
return false; // 查找失败
}
}
这里特别需要注意的一点就是这里的Node*添加了一个引用,这样的话就可以让我们在插入的时候在该函数栈帧中传入root->_left或者root->_right,在这种情况下虽然我们操作的是要插入的位置,但是这个结点是链接在整棵二叉搜索树中的,当我们对这个结点进行了修改,我们同样也对整棵树完成了修改。
删除
二叉搜索树的删除是操作中最为复杂的一个,它有很多的情况需要进行讨论。
首先查找元素时候存在于二叉搜索树中,如果不存在就返回,否则就删除该结点,在删除时可能会分为以下的四种情况:
- 要删除的结点无孩子结点;
- 要删除的结点只有左孩子结点;
- 要删除的结点只有右孩子结点;
- 要删除的结点有左右孩子结点;
首先我们来看一下非递归的版本:
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = cur; // 这里不能为空,否则删除根节点的时候就会发生报错
//Node* parent = nullptr; // 这里为空时在删除根节点的时候需要进行处理
while (cur) {
if (cur->_key < key) {
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key) {
parent = cur;
cur = cur->_left;
}
else { // 找到了要删除的结点
// 要删除的节点左节点为空值
if (cur->_left == nullptr) {
// 如果要删除的结点是根结点,且根结点的左子树为空,那么就需要对_root进行修改
if (cur == parent) { // cur == _root
//cur = cur->_right; // err
_root = cur->_right;
}
if (parent->_left == cur) {
parent->_left = cur->_right;
}
else {
parent->_right = cur->_right;
}
delete cur;
}
// 要删除的节点右节点为空值
else if (cur->_right == nullptr) {
// 如果要删除的结点是根结点,且根结点的右子树为空,那么就需要对_root进行修改
if (cur == parent) { // cur == _root
_root = cur->_left;
}
if (parent->_right == cur) {
parent->_right = cur->_left;
}
else {
parent->_left = cur->_left;
}
delete cur;
}
// 要删除的节点左右节点都不为空
else
{
// 找到左子树的最大值与右子树的最小值然后与要删除的数据进行替换
// 找右树最小节点替代,也可以是左树最大节点替代
//Node* pminRight = cur;
//Node* minRight = cur->_right;
//while (minRight->_left)
//{
// pminRight = minRight;
// minRight = minRight->_left;
//}
//cur->_key = minRight->_key;
//if (pminRight->_left == minRight)
//{
// pminRight->_left = minRight->_right;
//}
//else
//{
// pminRight->_right = minRight->_right;
//}
//delete minRight;
// 找左子树的最大值 - 左子树不为空
Node* pmaxLeft = cur;
Node* maxLeft = cur->_left;
while (maxLeft->_right) {
pmaxLeft = maxLeft;
maxLeft = maxLeft->_right;
}
cur->_key = maxLeft->_key;
if (pmaxLeft->_right == maxLeft) {
pmaxLeft->_right = maxLeft->_left;
}
else { // pmaxLeft->left == maxLeft;
pmaxLeft->_left = maxLeft->_left;
}
delete maxLeft;
}
return true;
}
}
return false;
}
然后是递归的版本:
bool _EraseR(Node*& root, const K& key) {
if (root == nullptr) // 若结点为空返回false
return false;
if (root->_key < key){ // 结点的值小于搜索的值,在右树中查找
return _EraseR(root->_right, key);
}
else if (root->_key > key) { // 结点的值大于搜索的值,在左树中查找
return _EraseR(root->_left, key);
}
else { // root->_key = key
Node* del = root;
// 开始准备删除
// 左节点为空
if (root->_left == nullptr) {
root = root->_right; // 将右孩子链接到的自己原本的位置,使用了引用
}
// 右节点为空
else if (root->_right == nullptr) {
root = root->_left; // 将左孩子链接到的自己原本的位置
}
else { // 左右节点都不为空
Node* maxleft = root->_left; // 寻找左树的最大结点
while (maxleft->_right) {
maxleft = maxleft->_right;
}
swap(root->_key, maxleft->_key); // 要删除的节点与左子树的最大值进行交换
return _EraseR(root->_left, key); // 转换成子树去删除
//return _EraseR(maxleft->_left, key); // maxleft是一个局部变量,使用引用传入会发生报错,当直接传入maxleft时,将maxleft删除,引用无法起作用,它的父节点没有办法链接到空。
}
delete del;
return true;
}
}
中序遍历
中序遍历二叉搜索树得到的是一个已经排好了序的数组,
// 为了调用的更加便捷使用了一种嵌套函数的方法
void InOrder()
{
_InOrder(_root);
}
void _InOrder(Node* root) // 当前函数不好调用,不能使用缺省参数应为缺省参数的形式:Node* root = _root 因为没有this指针无法调用_root。_root是一个变量而缺省参数需要的是常量
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
拷贝构造
BSTree(const BSTree<K>& t)
{
_root = Copy(t._root);
}
Node* Copy(Node* root) {
if (root == nullptr)
return nullptr;
Node* newRoot = new Node(root->_key);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
BSTree() = default; // 强制生成默认构造
赋值重载
// 使用拷贝构造形成的临时变量赋值给*this
BSTree<K>& operator=(BSTree <K> t)
{
swap(_root, t._root);
return *this;
}
析构
void Destory(Node*& root)
{
if (root == nullptr)
return;
Destory(root->_left);
Destory(root->_right);
delete root;
root = nullptr;
}