【二叉树进阶】搜索二叉树(递归+非递归两种版本详解)

文章目录

  • 前言
  • 1. 二叉搜索树的概念
  • 2. 二叉搜索树的结构
    • 2.1 结点结构
    • 2.2 树结构
  • 3. 插入操作(非递归)
    • 3.1 思路分析
    • 3.2 代码实现
    • 3.3 中序遍历(测试用)
  • 4. 查找操作(非递归)
    • 4.1 思路分析
    • 4.2 代码实现
  • 5. 删除操作(非递归)-重难点
    • 5.1 情况分类及思路分析
    • 5.2 代码实现
  • 6. 查找(递归版本)
    • 6.2 思路分析
    • 6.2 代码实现
  • 7. 插入(递归版本)
    • 7.1 思路分析
    • 7.2 代码实现
  • 8. 删除(递归版本)
    • 8.1 思路分析
    • 8.2 代码实现
  • 9. 其它相关成员函数的实现
    • 9.1 析构
    • 9.2 构造和拷贝构造
    • 9.3 赋值重载
  • 10. 完整代码
    • 10.1 BSTree.h
    • 10.2 BSTree.cpp

前言

二叉树在前面C数据结构阶段已经讲过,本节取名二叉树进阶是因为:

  1. map和set特性需要先铺垫二叉搜索树,而二叉搜索树也是一种树形结构。
  2. 二叉搜索树的特性了解,有助于更好的理解map和set的特性。
  3. 二叉树中部分面试题稍微有点难度,在前面讲解大家不容易接受,且时间长容易忘。
  4. 有些OJ题使用C语言方式实现比较麻烦,比如有些地方要返回动态开辟的二维数组,非常麻烦。

因此本节用C++语言对二叉树部分进行收尾总结。

我们之前学的普通的二叉树其实意义不大,因为如果只是用二叉树来存储数据的话,还不如直接用链表或者顺序表等这些顺序结构。

那二叉树搜索树相对来说,就比较有意义了。

1. 二叉搜索树的概念

那什么是二叉搜索树呢,先来了解一下它的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
在这里插入图片描述

  1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  3. 它的左右子树也分别为二叉搜索树(即它的每一棵子树也满足其左子树所有结点的值都小于根结点的值,右子树的所有结点的值大于根结点的值)

在这里插入图片描述

为什么又叫二叉排序树呢?

仔细观察我们会发现如果对一棵搜索二叉树进行中序遍历的话
在这里插入图片描述
其实就能得到一个结点值的升序序列。

那了解了搜索二叉树的概念,接下来我们就来手撕一个搜索二叉树。

2. 二叉搜索树的结构

首先我们来定义一下结点和搜索二叉树的结构

我们之前定义一个模板类,一般都喜欢用T(type),但是在这里比较喜欢用K(key)

2.1 结点结构

在这里插入图片描述

2.2 树结构

在这里插入图片描述

相信大家很容易都能看懂,那这里我就不详细解释了。

3. 插入操作(非递归)

接下来我们来实现一下向搜索二叉树中插入元素的操作。

3.1 思路分析

首先对于搜索二叉树来说,它的插入应该有插入成功和插入失败(因为搜索二叉树一般不允许出现重复元素)两种情况。

我们来分析一下

首先看插入成功的情况:

在搜索二叉树中,要插入一个元素时,如果可以 插入,那么它插入的位置一定是确定的。
举个栗子
在这里插入图片描述
还是以这棵树为例,假设我们现在要插入一个12
那要怎么做呢?
其实就是从根结点开始,去找出那个合适的位置,然后把12放进去。
根结点的值是8,12大于8,所以应该去右子树找,8的右子树是10,12依然大于10,那再看10的右子树,是14,此时12小于14,所以就要往14的左子树,14的左子树为13,12小于13,所以再看13的左子树,是空。
所以,12就应该放在13的左子树上。

在这里插入图片描述
此时就插入成功了

那失败的情况呢?

比如,我们现在要插入一个13
在这里插入图片描述
首先还是根据大小去比较,找合适的位置,但是走到13的位置发现要插入的值和已经存在的值相等,那就直接return false,插入失败。

当然,如果插入的是第一个结点,那就不需要比较了,直接让它成为根结点。

3.2 代码实现

那我们来写一下代码
在这里插入图片描述

首先第一个插入的结点是比较特殊的,因为第一个要作为根结点:

那怎么判断是不是第一个插入的呢?
🆗,插入第一个的时候,根结点是不是还是空的啊
所以
在这里插入图片描述
如果根结点为空,那就证明是第一次插入,就把它作为根结点。

那其它情况呢?

那后续的插入,就需要我们从根结点开始比较大小去找到合适的位置插入了,思路上面已经讲过了,这里就直接写代码了
在这里插入图片描述
按照我们上面讲的思路,走到红框这里,就走到key要插入的正确位置了。

那现在问题来了,如何正确的插入key对应的结点并链接到搜索二叉树上?

大家看这样可以吗
在这里插入图片描述
有没有什么问题?

🆗,这样写会存在两个问题:
第一个

这里的cur是一个函数里面的局部变量,函数调用结束,cur这个指针变量就被销毁了,销毁了不说,目前我们这样写是不是还会存在内存泄漏啊,cur被销毁了,但是它指向的空间还没释放。
那cur指向的空间不是属于这棵二叉树了嘛,不是最终可以随着搜索树的析构释放吗?
🆗,你把key的结点赋给cur,就链接到树上了嘛,并没有

这就是第二个问题:

把结点给cur,并没有链接到树上。
还看这个例子
在这里插入图片描述
走到上面红框的地方,cur只是存了13这个结点(假设取名为parent)的左孩子指针(即parent->left)的一个指针变量,相当于是parent->left的拷贝,把结点赋给它,并没有真正链接到13的左孩子上。

那怎么解决呢?

🆗,我们在cur不断向左或向右去找到过程中,记录它的父亲结点,最终cur走到正确的位置,把key的结点直接链接到父亲结点上就可以了
我们来修改一下
在这里插入图片描述

代码:

	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->left;}else if (key > cur->_key){parent = cur;cur = cur->right;}else{return false;}}//走到这里cur为空,就是key应该插入的位置cur = new Node(key);//链接if (key < parent->_key)parent->left = cur;if (key > parent->_key)parent->right = cur;return true;}

实现好了,行不行呢?测试一下:

构建我们示例中的那棵树,看看结果一不一样:
在这里插入图片描述
在这里插入图片描述
那我们构建好可以中序遍历打印一下看看,中序遍历刚好是一个升序。

3.3 中序遍历(测试用)

那我们写个中序遍历

二叉树的遍历我们在初阶已经学过,相信现在对于大家已经很简单了
在这里插入图片描述
ps:之前left和right没加_,现在补上了

那写好我就可以调了,但会有一个问题:

在这里插入图片描述
这里调用得传根结点(因为要递归,这个参数不能省),但是类外无法访问私有成员_root,把它放成共有的不太好。

那大家思考一下这里可不可以给缺省值?

缺省值给一个_root不就行了。
🆗是不行的,因为缺省值必须是常量或者全局变量(但一般不用全局变量)
这个我们在C++的第一篇文章有提到,大家可以去复习。
而且在参数列表其实根本拿不到成员变量_root,因为访问非静态成员要用this指针,而this指针只能在成员函数内部使用,参数列表也不行。

两个解决方法:

提供一个GetRoot的成员函数/方法,传参的时候通过该方法获取_root。
但C++里不喜欢这样
那其实有另外一种比较简便得方法,给InOrder函数在套一层(封装一下)
在这里插入图片描述
这样调的时候就不用传参了,当然我就可以把_InOrder变成私有的了

然后我们来测试一下

在这里插入图片描述
就可以了。

4. 查找操作(非递归)

接着我们来看一下查找:

4.1 思路分析

那查找其实是比较简单的:
我们要查找某个结点,那就从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找,找到的话返回true
在这里插入图片描述
最多查找高度次,走到空,还没找到,则这个值不存在,返回false
在这里插入图片描述

4.2 代码实现

写一下代码:

	bool Find(const K& key){Node* cur = _root;while (cur){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}else{return true;}}return false;}

测试一下:

在这里插入图片描述
没问题

5. 删除操作(非递归)-重难点

那如果要删除二叉搜索树中的某个结点,应该怎么处理呢?

5.1 情况分类及思路分析

首先查找元素是否在二叉搜索树中,如果不存在,则直接返回false, 否则要删除的结点可能分下面四种情况:

  1. 要删除的结点无孩子结点

比如:
在这里插入图片描述
我们现在要删除4这个结点
那这种情况是不是直接删除就好了,把4这个结点释放,让6的左孩子指向空就行了。

  1. 要删除的结点只有右孩子结点,左为空

比如我们现在要删除6
在这里插入图片描述
6要怎么删,那这里关键在于把6删了他还有一个孩子怎么处理?
🆗,现在这种情况我们要删除的结点6只有一个孩子,而6被删除的话它的父亲结点3就只剩下一个孩子了,而二叉树中一个结点最多可以有两个孩子,所以6被删除之后,我们是不是可以把它的孩子7托管给6的父亲3的右孩子结点啊。
在这里插入图片描述
不一定都是托给右,要进行判断,要看被删除结点是父结点的左还是右孩子
在这里插入图片描述

  1. 要删除的结点只有左孩子结点,右为空

在这里插入图片描述
比如我们现在要删除14
那这种情况处理方法其实和上一个一样,也是把被删除结点的孩子托管给其父亲结点,那这里也是托给父亲的右孩子。
另外其实第一种情况也可以归到第二种或第三种里面,第一种是两个孩子都为空,那也可以归到左为空或者右为空的情况里面。

  1. 要删除的结点有左、右孩子结点

在这里插入图片描述
比如我们要删除3或8,怎么删?
首先这里我们上面用到的托管给父结点的方法就不管用了,因为每个结点最多管两个孩子,而现在要删除的这个结点就有两个孩子,如果父亲原本就有两个,那这样父亲要管三个就超了。
况且对于8也就是根结点来说,它根本没有父结点。

那这种情况该如何处理呢?

对于这种情况我们使用的方法叫做——替换法删除法/伪删除法

在这里插入图片描述
以删除8为例,大家看,如果把8删了,谁能够坐到8这个位置呢?
那对于8这棵树来说,其实这个替代者可以有两个人选:

  1. 左子树的最大值
  2. 右子树的最小值

在这里插入图片描述
那对于当前这棵树其实就是7或者10。
其实仔细观察我们会发现,对于一棵搜索二叉树来说
整棵树中最大的结点就是最右边的那个结点,最小的结点就是最左边的那个结点。
那对于子树来说也是这样,我们看到7其实就是左子树的最右边的结点,10就是右子树最左边的结点。
所以这里,要想8,可以选择用7替换也可以用10。

以用7为例,怎么进行替换删除呢?

在这里插入图片描述
把7这个结点的值赋给8这个结点,然后把原始的7结点删除了就行了

那总结一下:

虽然上面我们分析了四种情况,但是我们说了第一种即被删除结点没有孩子的情况可以归到左为空或者右为空的情况里面。
所以总共有三种:

  1. 左为空
  2. 右为空
    这两种都是托管,但注意具体的代码处理是不一样的,因为一个右为空,一个左为空。
  3. 左右都不为空
    替换删除/伪删除法

5.2 代码实现

那下面写一下代码吧

首先我们得查找要删除的元素:

在这里插入图片描述

那接下来就是实现删除的逻辑了

左为空或者右为空得删除其实比较好处理,托管给父亲结点即可

在这里插入图片描述

最后来实现一下比较难搞的替换删除:

那我们上面说了这个替换结点可以是左数得最大结点(最右边的那个结点),也可以是右树得最小结点(最左边)。
那这里我选择右树的最小结点。
这里的替换删除分为两步:
第一步——找到右树最小结点,然后替换要删除的结点
在这里插入图片描述
第二步——删除替换结点
那这里其实有一些需要注意的地方:
我们在这里选择的是右树的最小结点,即右子树最左边的结点,那既然是最左边,他一定没有左孩子了,但是,它可能会有右孩子。
比如像这样
在这里插入图片描述
所以这里删除替换结点也要用托管的方式去删,那就需要保存一下替换结点的父亲
那这里还有一些需要特别注意的点:
首先我问大家,现在不是需要保存替换结点的父结点吗?这个父结点的初始值可以给nullptr吗?
在这里插入图片描述
如果看上面那个例子是可以的,因为会进入循环更新parent的值。
但是如果是这样的情况呢?
在这里插入图片描述
大家看这种情况是不是不会进入while循环啊,所以pminRight不会更新,那后面托管的时候就会对空指针解引用,所以这里初始值可以赋cur,即要进行删除的那个结点(伪删除)。
Node* pminRight = cur;
另外我们上面的那个例子在这里插入图片描述
就这个,最终托管的时候是链接到父亲的左子树上,但是不要认为这里找到是最左结点就一定链接到左子树上。
我们现在这个例子:
在这里插入图片描述
是不是就是链接到pminRight的右子树上了,所以要去看minRight(即替换结点)在pminRight的那边。
所以补充完毕应该是这样的
在这里插入图片描述
最终删除成功,就return true。

但是呢,其实改到现在还有一个问题:

在这里插入图片描述
我们能看出来这个测试结果是不正确的。

问题出在哪里呢?

在这里插入图片描述
我们直接删除根是没什么问题的现在
在这里插入图片描述
但是,如果这样
在这里插入图片描述
大家看这种其实就是挂了,我们见过很多次了,调试一下
在这里插入图片描述
出现了一个空指针异常(这里是在删除8的时候跳出来这个异常的),怎么回事呢?

我们来分析一下:

在这里插入图片描述
这次我们先删除了10、14、13,所以在删除8的时候是这样的
在这里插入图片描述
那为什么此时再去删除8就出现parent是空指针的情况呢?
🆗,那问题在于
现在要删8,是右为空的情况
在这里插入图片描述
因为根结点没有父亲结点。
那我们怎么解决呢?
那对于这种情况我们可以单独处理一下,其实可以直接更改一下根结点,直接让3成为新的根,然后把8删了就行
在这里插入图片描述
在这里插入图片描述

那然后我们再来测试

在这里插入图片描述
就可以了,随便删
在这里插入图片描述

bool Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{//key == cur->_key就是找到了,进行删除//1.左为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;}//2.右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (cur == parent->_left)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;}else//左右都不为空{//这里选择右树的最小结点(最左边)替换Node* pminRight = cur;Node* minRight = cur->_left;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;}return true;}}//找不到直接返回falsereturn false;
}

那搜索二叉树主要的操作其实就这些,但是呢,我们上面都是用循环实现的,那搜索二叉树这里呢其实也可以用递归去搞,这三个操作的递归实现我们也有必要去学一下。

6. 查找(递归版本)

查找用递归要怎么写呢?

在这里插入图片描述
在类里面定义的递归一般我们都要套一层写成这种,原因就和我们上面写中序遍历那里一样。

6.2 思路分析

那具体怎么实现呢?
我们来分析一下:

首先来回顾一下递归的思想:
它通常把一个大型复杂问题层层转化为一个与原问题相似的规模较小的问题来求解,每个子问题都可以进一步分解为更小的子问题,直到达到基本情况(终止条件),然后逐层返回结果,最终得到整个问题的解。
所以这里的思路是这样的:
查找嘛,那就从根结点开始,如果大于根结点的值,就转换为去右子树里面查找,如果小于根结点,就转换为去左子树查找,如果等于就直接返回;那对于子树也是这样,一步步转换为子问题。
如果最后都没找到,一直到空,那就返回false。

那我们写一下代码:

6.2 代码实现

这个没什么难度,我就直接上代码了:

bool FindR(const K& key)
{return _FindR(_root, key);
}bool _FindR(Node* root, const K& key)
{if (root == nullptr)return false;if (key < root->_key){return _FindR(root->_left, key);}else if (key > root->_key){return _FindR(root->_right, key);}else{retrun true;}
}

试一下:

在这里插入图片描述

7. 插入(递归版本)

7.1 思路分析

在这里插入图片描述

那插入用递归怎么做呢?

那也是类似的思路,从根节点开始,如果要插入的结点大于根,就转换为去右子树插入;如果要插入的结点小于根,就转换为去左子树插入;如果相等,返回flase;如果一直走到空,那就就在该位置插入就行了。

7.2 代码实现

在这里插入图片描述

但是有一个问题,我们找到空插入的时候,如何和它的父亲链接起来?

我们可能会想到这样的方法,比如把父亲作为递归的参数进行传递,或者去判断root的子树为空而不是它本身为空。
但是,最好的方法我觉得是这样:
在这里插入图片描述
直接用root的引用就可以了。
因为引用的话,走到空,他就是那个位置指针的引用,直接赋给它就链接上了。
还不用像上面循环实现的那样去判断要连接到那边。

那大家思考一下,我们上面循环的方式,可以用引用吗?
不可以的,因为C++中的引用是不能改变指向的,引用一旦引用一个实体之后,就不能再引用其他实体
而这里递归的话,每次递归都相当于创建了一个新的引用,而不是改变上一层的引用的指向。

测试一下:

在这里插入图片描述

8. 删除(递归版本)

然后是删除:
在这里插入图片描述

8.1 思路分析

怎么实现呢?

那其实大致的思路还是一样的,从根结点开始判断,如果要删除的值大于根,转换为去右子树删,小于根,转换为去左子树删,等于,就进行删除,如果走到空,那就是找不到。
在这里插入图片描述
所以关键还是在于如何进行删除。

8.2 代码实现

那我们来分析一下:

其实还是我们上面分析的那三种情况:
左为空、右为空或者左右都不为空。
那这里我们用引用,写起来还是比较简便的。
先写一下左为空和右为空的情况,这两个比较好处理
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

然后看一下比较麻烦的左右都不为空的情况

我们之前非递归版本的实现是,找一个符合条件的结点替换它,然后把替换的结点删除掉
在这里插入图片描述
这里也可以用同样的方法,但我觉得比较简便的方法是这样的:
还是先找一个可以替代要删除结点的结点(左子树最大结点或右子树最小),以左子树最大结点替换为例
在这里插入图片描述
交换它们两个的值,然后删除左子树里面的8(key)这个结点就行了
在这里插入图片描述

测试一下:

在这里插入图片描述
没问题

bool EraseR(const K& key)
{return _EraseR(_root, key);
}bool _EraseR(Node*& root, const K& key)
{if (root == nullptr)return false;if (key < root->_key){return _EraseR(root->_left, key);}else if (key > root->_key){return _EraseR(root->_right, key);}else{//删除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(maxLeft->_key, root->_key);return _EraseR(root->_left, key);}delete del;return true;}
}

9. 其它相关成员函数的实现

如果我们想在相对搜索二叉树的对象进行拷贝构造可以吗?

在这里插入图片描述
是可以的,虽然我们没写,但是拷贝构造属于默认成员函数,编译器会自动生成,不过默认生成的只完成浅拷贝。
在这里插入图片描述
现在没有报错的原因是因为我们没写析构,如果有析构就会出问题,因为搜索二叉树涉及资源申请,这样如果是浅拷贝的话,在析构的时候就会对一块空间析构两次,所以就会出问题。
这都是我们之前学过的内容。

9.1 析构

那我们可以先来写一下析构。

那析构的话我们这里还是用递归来搞,也可以用循环,但是比较麻烦:

在这里插入图片描述
那实现一下Destory就行了,关于二叉树的销毁我们初阶也讲过,比较好的做法是后续销毁
在这里插入图片描述

那现在不出意外,有了析构我们的浅拷贝就要出错了

在这里插入图片描述
调式一下
在这里插入图片描述

9.2 构造和拷贝构造

那我们就来写一个深拷贝的拷贝构造,我们还是用递归来实现

那也比较简单,先序去拷贝创建就行了
在这里插入图片描述

那有了拷贝构造我们得实现一下构造函数:

在这里插入图片描述
因为现在有了拷贝构造,编译器就不会生成默认的构造函数了,那就需要我们自己写了
在这里插入图片描述
另外C++11有一个关键字——default,可以强制编译器生成默认构造,这个我们后面也会讲
在这里插入图片描述
那有了默认构造,我们下面给了缺省值,它走初始列表的时候就会用缺省值去初始化。
在这里插入图片描述
现在就可以了。

9.3 赋值重载

那赋值重载我们也搞一下吧:

跟我们之前玩的一样

BSTree<K>& operator=(const BSTree<K> t)
{swap(_root, t._root);return *this;
}

在这里插入图片描述

那关于搜索二叉树的实现差不多就到这里了

10. 完整代码

gitee

10.1 BSTree.h

#pragma oncetemplate <class K>
struct BSTreeNode
{BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){}BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;
};template <class K>
class BSTree
{typedef BSTreeNode<K> Node;
public: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->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{return false;}}//走到这里cur为空,就是key应该插入的位置cur = new Node(key);//链接if (key < parent->_key)parent->_left = cur;if (key > parent->_key)parent->_right = cur;return true;}bool Find(const K& key){Node* cur = _root;while (cur){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}else{return true;}}return false;}bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{//key == cur->_key就是找到了,进行删除//1.左为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;cur = nullptr;}//2.右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (cur == parent->_left)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;cur = nullptr;}else//左右都不为空{//这里选择右树的最小结点(最左边)替换Node* pminRight = cur;Node* minRight = cur->_left;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;minRight = nullptr;}return true;}}//找不到直接返回falsereturn false;}void InOrder(){_InOrder(_root);cout << endl;}bool FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}/*BSTree():_root(nullptr){}*/BSTree() = default;~BSTree(){Destory(_root);}BSTree(const BSTree<K>& t){_root = copy(t._root);}BSTree<K>& operator=(const BSTree<K> t){swap(_root, t._root);return *this;}
protected: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;}void Destory(Node*& root){if (root == nullptr)return;Destory(root->_left);Destory(root->_right);delete root;root = nullptr;}bool _EraseR(Node*& root, const K& key){if (root == nullptr)return false;if (key < root->_key){return _EraseR(root->_left, key);}else if (key > root->_key){return _EraseR(root->_right, key);}else{//删除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(maxLeft->_key, root->_key);return _EraseR(root->_left, key);}delete del;return true;}}bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (key > root->_key){return _InsertR(root->_right, key);}else if (key < root->_key){return _InsertR(root->_left, key);}else{return false;}}bool _FindR(Node* root, const K& key){if (root == nullptr)return false;if (key < root->_key){return _FindR(root->_left, key);}else if (key > root->_key){return _FindR(root->_right, key);}else{return true;}}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}
private:Node* _root = nullptr;
};

10.2 BSTree.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include "BSTRee.h"void BSTreeTest1()
{BSTree<int> t1;int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };for (auto e:a){t1.Insert(e);}t1.InOrder();cout << t1.FindR(10) << endl;cout << t1.FindR(19) << endl;}
void BSTreeTest2()
{BSTree<int> t1;int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };for (auto e : a){t1.Insert(e);}t1.InOrder();t1.Erase(10);t1.InOrder(); t1.Erase(14);t1.InOrder();t1.Erase(13);t1.InOrder();t1.Erase(8);t1.InOrder();for (auto e : a){t1.Erase(e);t1.InOrder();}
}
void BSTreeTest3()
{BSTree<int> t1;int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };for (auto e : a){t1.InsertR(e);}t1.InOrder();for (auto e : a){t1.EraseR(e);t1.InOrder();}}
void BSTreeTest4()
{BSTree<int> t1;int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };for (auto e : a){t1.InsertR(e);}t1.InOrder();BSTree<int> t2=t1;t2.InOrder();}
int main()
{BSTreeTest4();return 0;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/14078.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【设计模式】观察者设计模式解析

目录 一、观察者模式定义 二、观察者模式角色 三、观察者模式类图 四、观察者模式实例 五、观察者模式优缺点 5.1、优点 5.2、缺点 六、观察者模式应用 6.1、Spring 中观察者模式的四个角色 6.2、coding~~~~~~ 一、观察者模式定义 观察者模式(Observer Pattern)&#…

【如何训练一个中英翻译模型】LSTM机器翻译模型训练与保存(二)

系列文章 【如何训练一个中英翻译模型】LSTM机器翻译seq2seq字符编码&#xff08;一&#xff09; 【如何训练一个中英翻译模型】LSTM机器翻译模型训练与保存&#xff08;二&#xff09; 【如何训练一个中英翻译模型】LSTM机器翻译模型部署&#xff08;三&#xff09; 【如何训…

【框架篇】Spring Boot 日志

Spring Boot 日志 一&#xff0c;日志用途 尽管一个项目在没有日志记录的情况下可能能够正常运行&#xff0c;但是日志记录对于我们来说却是至关重要的&#xff0c;它存在以下功能&#xff1a; 1&#xff0c;故障排查和调试&#xff1a;当项目出现异常或者故障时&#xff0c;…

idea连接远程服务器上传war包文件

idea连接远程服务器&上传war包 文章目录 idea连接远程服务器&上传war包1. 连接服务器2.上传war包 1. 连接服务器 选择Tools -> Start SSH Session 添加配置 连接成功 2.上传war包 Tools -> Deployment -> Browse Remote Host 点击右侧标签&#xff0c;点击&…

gin框架内容(三)--中间件

gin框架内容&#xff08;三&#xff09;--中间件 Gin框架允许开发者在处理请求的过程中&#xff0c;加入用户自己的函数。这个函数就叫中间件&#xff0c;中间件适合处理一些公共的业务逻辑&#xff0c;比如登录认证、权限校验、数据分页、记录日志、耗时统计等 即比如&#x…

使用分布式HTTP代理爬虫实现数据抓取与分析的案例研究

在当今信息爆炸的时代&#xff0c;数据已经成为企业决策和发展的核心资源。然而&#xff0c;要获取大规模的数据并进行有效的分析是一项艰巨的任务。为了解决这一难题&#xff0c;我们进行了一项案例研究&#xff0c;通过使用分布式HTTP代理爬虫&#xff0c;实现数据抓取与分析…

华为eNSP:isis的配置

一、拓扑图 二、路由器的配置 配置接口IP AR1&#xff1a; <Huawei>system-view [Huawei]int g0/0/0 [Huawei-GigabitEthernet0/0/0]ip add 1.1.1.1 24 [Huawei-GigabitEthernet0/0/0]qu AR2: <Huawei>system-view [Huawei]int g0/0/0 [Huawei-GigabitEthe…

【用IDEA基于Scala2.12.18开发Spark 3.4.1 项目】

目录 使用IDEA创建Spark项目设置sbt依赖创建Spark 项目结构新建Scala代码 使用IDEA创建Spark项目 打开IDEA后选址新建项目 选址sbt选项 配置JDK debug 解决方案 相关的依赖下载出问题多的话&#xff0c;可以关闭idea&#xff0c;重启再等等即可。 设置sbt依赖 将sbt…

QTday4(鼠标事件和键盘事件/QT实现连接TCP协议)

笔记 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QDebug> #include <QTcpServer>//服务器类 #include <QTcpSocket>//客户端类 #include <QMessageBox> #include <QList>//链表容器QT_BEGIN_NAMESPACE namespace Ui …

单链表详解

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大家三连关注&#xff0c;一起学习&#xff0c;一起进步&#…

Mac 安装启动RabbitMq

使用HomeBrew安装 未安装的请参照我的这篇Mac安装HomeBrew文章 安装 执行命令 brew install rabbitmq启动方式 brew services start rabbitmq端口说明 端口用处5672RabbitMQ通讯端口&#xff0c;也就是连接使用的端口15672RabbbitMQ管理界面端口&#xff0c;需要开启Manage…

web自动化测试-PageObject 设计模式

为 UI 页面写测试用例时&#xff08;比如 web 页面&#xff0c;移动端页面&#xff09;&#xff0c;测试用例会存在大量元素和操作细节。当 UI 变化时&#xff0c;测试用例也要跟着变化&#xff0c; PageObject 很好的解决了这个问题。 使用 UI 自动化测试工具时&#xff08;包…

Zebec Card 将在亚洲、拉美等地区推出,生态全球化加速

随着以Visa、特斯拉、BNY Mellon、BlackRock、Mastercard、Gucci等为代表的传统商业机构巨头&#xff0c;以及萨尔瓦多、中非共和国等为代表的国家不断的向加密货币领域布局&#xff0c;越来越多的投资者开始以新的眼光来看待加密货币&#xff0c;仅在2022年&#xff0c;加密货…

如何学好Java并调整学习过程中的心态:学习之路的秘诀

文章目录 第一步&#xff1a;建立坚实的基础实例分析&#xff1a;选择合适的学习路径 第二步&#xff1a;选择合适的学习资源实例分析&#xff1a;参与编程社区 第三步&#xff1a;动手实践实例分析&#xff1a;开发个人项目 调整学习过程中的心态1. 不怕失败2. 持续学习3. 寻求…

Unity自定义后处理——Tonemapping色调映射

大家好&#xff0c;我是阿赵。   继续介绍屏幕后处理&#xff0c;这一期介绍一下Tonemapping色调映射 一、Tone Mapping的介绍 Tone Mapping色调映射&#xff0c;是一种颜色的映射关系处理&#xff0c;简单一点说&#xff0c;一般是从原始色调&#xff08;通常是高动态范围&…

SpringBoot 如何进行 统一异常处理

在Spring Boot中&#xff0c;可以通过自定义异常处理器来实现统一异常处理。异常处理器能够捕获应用程序中抛出的各种异常&#xff0c;并提供相应的错误处理和响应。 Spring Boot提供了ControllerAdvice注解&#xff0c;它可以将一个类标记为全局异常处理器。全局异常处理器能…

【动态规划】子数组系列

文章目录 动态规划&#xff08;子数组系列&#xff09;1. 最大子数组和2. 环形子数组的最大和3. 乘积最大子数组4. 乘积为正的最长子数组的长度5. 等差数列划分6. 最长湍流子数组7. 单词拆分8. 环形字符串中的唯一的子字符串 动态规划&#xff08;子数组系列&#xff09; 1. 最…

算法与数据结构(四)--排序算法

一.冒泡排序 原理图&#xff1a; 实现代码&#xff1a; /* 冒泡排序或者是沉底排序 *//* int arr[]: 排序目标数组,这里元素类型以整型为例; int len: 元素个数 */ void bubbleSort (elemType arr[], int len) {//为什么外循环小于len-1次&#xff1f;//考虑临界情况&#xf…

Neo4j 集群和负载均衡

Neo4j 集群和负载均衡 Neo4j是当前最流行的开源图DB。刚好读到了Neo4j的集群和负载均衡策略&#xff0c;记录一下。 1 集群 Neo4j 集群使用主从复制实现高可用性和水平读扩展。 1.1 复制 集群的写入都通过主节点协调完成的&#xff0c;数据先写入主机&#xff0c;再同步到…

振弦采集仪及在线监测系统完整链条的岩土工程隧道安全监测

振弦采集仪及在线监测系统完整链条的岩土工程隧道安全监测 近年来&#xff0c;随着城市化的不断推进和基础设施建设的不断发展&#xff0c;隧道建设也日益成为城市交通发展的必需品。然而&#xff0c;隧道建设中存在着一定的安全隐患&#xff0c;如地质灾害、地下水涌流等&…