从零开始C++----九【C++ 数据结构】搜索二叉树(BST)全解析:从定义到实现,一篇搞定

系列文章目录

提示:这里是系列文章的专栏
并不喜欢吃鱼的C++专栏


提示:以下是文章目录哦!

文章目录

系列文章目录

前言

一、搜索二叉树的概念

[1.先看左边的树(合法 BST)](#1.先看左边的树(合法 BST))

[2.再看右边的树(不合法 BST)](#2.再看右边的树(不合法 BST))

[二、 二叉搜索树的性能分析](#二、 二叉搜索树的性能分析)

1.最好情况:

2.最坏情况:

3.二叉搜索树,链表,数组的效率对比

三、二叉搜索树的实现

1.二叉搜索树模版节点的实现

易错点

2.二叉搜索树模版树节点的实现

3.二叉搜索树的插入的实现

[3.1 插入位置的寻找](#3.1 插入位置的寻找)

[3.2 插入的实现](#3.2 插入的实现)

[3.3 插入的特殊位置实现](#3.3 插入的特殊位置实现)

4.二叉树查找功能的实现

5.二叉树的节点删除功能的实现

5.1直接删除法

5.2更换指向法

5.3替换删除法

6.搜索二叉树其余接口的实现

[6.1 中序遍历](#6.1 中序遍历)

[6.2 树的析构函数](#6.2 树的析构函数)

[6.3 树的拷贝函数](#6.3 树的拷贝函数)

[6.4 树的默认构造](#6.4 树的默认构造)


前言

提示:今天这里我们来新学一个数据结构哦


提示:以下是本篇文章正文内容,下面案例可供参考

一、搜索二叉树的概念

二叉搜索树(英文名:Binary Search Tree, BST)是一种满足以下性质的二叉树:

  • 左子树所有节点的值 小于 根节点的值
  • 右子树所有节点的值 大于 根节点的值
  • 左右子树也都是二叉搜索树
  • 关键推论:中序遍历结果是严格升序的序列
  • 二叉搜索树在设计时,既可以支持插入相等的值,也可以不支持插入相等的值,具体规则完全取决于实际使用场景和需求。

在后续我们会学习的 map/set/multimap/multiset 等标准容器中,它们的底层结构正是二叉搜索树:

map / set:不支持插入重复的键值,保证键的唯一性;

multimap / multiset:支持插入重复的键值,允许相同值多次存储

1.先看左边的树(合法 BST)

它完全符合 BST 的核心规则:

对于根节点 8:左子树所有节点都小于 8(3,1,6,4,7),右子树所有节点都大于 8(10,14,13)

对于节点 3:左 1 < 3,右 6 > 3

对于节点 6:左 4 < 6,右 7 > 6

对于节点 10:右 14 > 10

对于节点 14:左 13 < 14

中序遍历结果1, 3, 4, 6, 7, 8, 10, 13, 14,是严格升序的,完美验证了 BST 的性质

2.再看右边的树(不合法 BST)

这里有两处明显的错误,直接违反了 BST 的定义:

  1. 节点 6 的左孩子是 3
    • 节点 6 位于根节点 8 的左子树中,按规则,它的所有后代都必须小于 8,这点没问题。
    • 但它的父节点是 3,所以它的左孩子 3 并不小于父节点 3,而是等于,违反了 "左子树所有节点 < 根节点" 的规则(在不支持重复值的标准 BST 中)
  2. 节点 10 的右孩子是 10
    • 节点 10 是根节点 8 的右孩子,它的右孩子也是 10,不满足 "右子树所有节点> 根节点" 的规则,出现了重复值

所以,右边的树不满足二叉搜索树的定义


二、 二叉搜索树的性能分析

BST 的效率完全取决于树的高度

  • 最好情况(完全平衡):插入 / 查找 / 删除的时间复杂度是 O(log n),和二分查找一样高效
  • 最坏情况(退化成单链):时间复杂度会退化成 O(n),和普通数组遍历没区别

接下来讲讲最好情况和最坏情况是怎么出现的:

1.最好情况:

BST 的插入 / 查找 / 删除操作,本质上都是:

从根节点开始,每次和当前节点比较,然后往左或往右走,直到找到位置或空节点

在最好情况下(完全平衡):

  • 每次比较,都能排除掉一半的节点(往左走,就排除了当前节点 + 整个右子树;往右走,就排除了当前节点 + 整个左子树)
  • 这个过程和二分查找完全一样,每次问题规模减半

我们用节点总数 n 来推导一下:

2.最坏情况:

当你按有序序列(1,2,3,4...)插入节点时 ,树会变成一条斜链,完全失去 "二分" 的优势,1为根节点,然后2>1,因此为在1的右节点,随后3>2,以此类推就像上图的右边一样,将一直连下去退化成一条单链,当数据为n时,正好对应的时间复杂度就为O(n)


3.二叉搜索树,链表,数组的效率对比

| 对比维度 | 有序数组 | 链表 | 二叉搜索树(BST) |
| 查找效率 | 二分查找 O (log n) | 只能遍历 O (n) | 最好情况 O (log n) |
| 插入 / 删除效率 | 需移动元素 O (n) | 找到后操作 O (1) | 最好情况 O (log n) |

天然有序性 天生有序 无序 中序遍历直接得到有序序列

三、二叉搜索树的实现

1.二叉搜索树模版节点的实现

cpp 复制代码
template<class T>
struct BSTNode
{
    T _data;
    BSTNode<T>* _left;
    BSTNode<T>* _right;

    BSTNode(const T& data)
        : _data(data)
        , _left(nullptr)
        , _right(nullptr)
    {}
};
  1. 模板 template<class T>支持 int / double / string 任意类型,和 C++ STL set/map 设计一致
  2. 成员变量
  • _key:节点存储的关键字(比较大小依据)
  • _left:左孩子 → 所有值 < 当前节点
  • _right:右孩子 → 所有值 > 当前节点

3.构造函数必须手动初始化 _left_rightnullptr

易错点

  • 不初始化指针 → 野指针,运行崩溃
  • 写结构体时漏加 BSTNode<T> 模板参数

2.二叉搜索树模版树节点的实现

cpp 复制代码
template <class K>
class SBTree{
    using Node = BSTnode<K>;
    public:
	    ..................
    protected:
	    Node* _root = nullptr;
    };

这里的 using Node = SBTNode<K>是C++11新增的用法给 BSTNode<K> 这个类型起个新的、更短的名字 ,叫 Node

3.二叉搜索树的插入的实现

插入的具体过程如下:

  1. 树为空,则直接新增结点,赋值给 root 指针
  2. 树不为空,按照二叉搜索树性质,插入值比当前结点大则往右走,插入值比当前结点小则往左走,找到空位置后,插入新结点。
  3. 如果支持插入相等的值,插入值与当前结点相等时,可以统一往右走,也可以统一往左走,找到空位置后插入新结点**(需要注意保持逻辑一致性,插入相等的值不要一会儿往右走,一会儿往左走。)**

接下来我们用一个例子具体解释一下:

如上图的一个搜索二叉树,我们此时要插入一个数为16,既然要插入,那我们就得找到值为14的节点,不断满足二叉搜索树的性质向下查找:

如下图,我们在上图的基础上插入一个重复的数字3,根据二叉搜索树的性质往下寻找值为4的节点插入:

对于以上的插入,都有一个前提,需要我们找到被插入的节点 ,如上面两张图值为14的节点,和值为4的节点 ,但插入可能插到左边或者右边 ,以及如果这棵树一个节点也没有的特殊情况 ,可以分为4步来思考插入:

1.找到插入的位置 2.插入的实现 3.插入的特殊情况处理

3.1 插入位置的寻找

这里的cur和parent需要解释一下**,cur是用来遍历二叉树的,用来寻找需要插入的位置,最后走到空,**

每次往下寻找前都需要更新一下parent,parent是用来充当插入的新节点的父亲,当然有人说这里为

什么不能直接用cur来实现,一般我们遵循一个指针只干一件事情,因此cur用来寻找位置,parent用来

记录位置

cpp 复制代码
        Node* cur = _root;
        Node* parent = nullptr; 
// 2. 寻找合适插入位置
        while (cur != nullptr)
        {
            // 待插入值更小,往左走
            if (key < cur->_key)
            {
                parent = cur;
                cur = cur->_left;
            }
            // 待插入值更大,往右走
            else if (key > cur->_key)
            {
                parent = cur;
                cur = cur->_right;
            }
            // 存在重复值,set/map规则:不允许插入
            else
            {
                return false;
            }
        }

3.2 插入的实现

cpp 复制代码
    // 3. 新建节点,挂载到父节点
        cur = new Node(key);
        if (key < parent->_key)
            parent->_left = cur;
        else
            parent->_right = cur;

既然要插入,那就首先要创建新节点,根据搜索二叉树的性质,比当前parent节点值小的往左边插入,大的往右边插入

3.3 插入的特殊位置实现

cpp 复制代码
  // 1. 空树:直接新建根节点
        if (_root == nullptr)
        {
            _root = new Node(key);
            return true;
        }

如果为空,直接创建新节点即可

插入的完整代码如下:

cpp 复制代码
 // 1. 插入数据
    bool Insert(const T& key)
    {
        // 1. 空树:直接新建根节点
        if (_root == nullptr)
        {
            _root = new Node(key);
            return true;
        }

        Node* cur = _root;
        Node* parent = nullptr;

        // 2. 寻找合适插入位置
        while (cur != nullptr)
        {
            // 待插入值更小,往左走
            if (key < cur->_key)
            {
                parent = cur;
                cur = cur->_left;
            }
            // 待插入值更大,往右走
            else if (key > cur->_key)
            {
                parent = cur;
                cur = cur->_right;
            }
            // 存在重复值,set/map规则:不允许插入
            else
            {
                return false;
            }
        }

        // 3. 新建节点,挂载到父节点
        cur = new Node(key);
        if (key < parent->_key)
            parent->_left = cur;
        else
            parent->_right = cur;

        return true;
    }

4.二叉树查找功能的实现

查找的具体过程如下:

  1. 从根开始比较,查找 x,x 比根的值大则往右边走查找,x 比根值小则往左边走查找
  2. 最多查找高度次,走到空还没找到,说明这个值不存在
  3. 如果不支持插入相等的值,找到 x 即可返回
  4. 如果支持插入相等的值,意味着有多个 x 存在,一般要求查找中序的第一个 x

这里怕部分同学不太清楚:

在支持重复值的二叉搜索树(对应 multiset/multimap)中,当我们查找 x 时,"中序的第一个 x" 就是:在上面这个升序序列里,第一个出现的 x 对应的节点

以下图里的 x=3 为例:

  • 中序序列里,3 第一次出现的位置,是 1 的右孩子那个 3,也就是图中下方的那个红色节点
  • 而根节点左孩子的那个 3,是序列里第二个出现的 3

所以,查找 3 时,我们要返回的是中序遍历里第一个 3,也就是 1 的右孩子节点

代码具体实现如下:

cpp 复制代码
  // 2. 查找数据
    Node* Find(const T& key)
    {
        Node* cur = _root;
        while (cur != nullptr)
        {
            if (key < cur->_key)
                cur = cur->_left;
            else if (key > cur->_key)
                cur = cur->_right;
            else
                return cur; // 找到目标节点
        }
        return nullptr; // 遍历到空,查找失败
    }

5.二叉树的节点删除功能的实现

首先查找元素是否在二叉搜索树中,如果不存在,则返回 false。如果查找元素存在,则分以下四种情况分别处理:(假设要删除的结点为 N)

  1. 要删除结点 N 左右孩子均为空
  2. 要删除的结点 N 左孩子为空,右孩子结点不为空
  3. 要删除的结点 N 右孩子为空,左孩子结点不为空
  4. 要删除的结点 N 左右孩子结点均不为空

对于以上四种情况**,可具体总结为3种解决方案**:

假如要删除的节点为N,父节点为P,它指向N

5.1直接删除法

适用对象:树中所有没有子节点的叶子节点,包括树的根节点(当树只有一个节点时)

  • 处理方法
    1. 直接将父节点 P 指向 N 的指针置为 nullptr
    2. 释放节点 N 的内存
  • 核心逻辑:因为没有子节点,删除后不会影响其他节点,直接 "断链删除" 即可

5.2更换指向法

适用对象:只有左或者右孩子的节点

  • 处理方法
    1. 让父节点 P 原本指向 N 的指针,改为指向 N 的左/右孩子
    2. 释放节点 N 的内存
  • 核心逻辑 :用左/右孩子直接顶替 N 的位置,保持 BST 的大小关系不变

5.3替换删除法

适用对象:所有同时拥有左、右子树的节点

  • 处理方法(后继替换法,标准做法)
    1. 找后继节点 :在 N右子树中找到最左节点 (即中序遍历中 N 的下一个节点),记为 S
    2. 值替换 :将 N 的值替换为 S 的值
    3. 删除后继节点 :此时问题转化为删除节点 S,而 S 必然满足情况 1 或情况 2(左孩子为空),按对应方法删除即可
  • 核心逻辑 :不直接删除 N,而是用后继节点的值覆盖它,再删除更容易处理的后继节点,保证 BST 的有序性不被破坏

----------- 以下是这三种方法的运用展示-----------

对于值为1的节点删除 十分简单,直接采用直接删除法即可,随后让值为3的节点左指针指向nullptr

对于值为10的节点的删除要稍微复杂一点,这里很明显可以看到,它还有右孩子,因此**直接删除法不可行,这里就需要采用更换指向法,**将当前值为14的节点先记录下来,随后删除值为10的节点,将8的右指针指向值为14的节点即可

这里的删除方式,和上面删除10节点的方式一样,自行理解

对于值为3的节点的删除 ,观察可知,它还有左孩子和右孩子,因此直接删除法更换指向法 都不能直接完成删除,这里需要用到替换删除法 ,为了删除掉值为3的节点同时满足搜索二叉树的性质,我们需要找到一个比1,以及右子树都小的和比8小的节点:

首先来说比8大,那必须在值为8节点的左子树里寻找,而不能是右子树,因为右边一定比它大,如这里的10,14,13,那我们只能从左子树找,同时我们要大于1,因此不能往值为3的节点的左子树去找,得往右子树找,由于我们要找一个比右子树都小的,因此必须是右子树最左边的节点

再来看这里,我们要删除掉3.找到右子树最左边的值为4的节点,首先将值为4的节点覆盖到3上,随后将替换前的值为4的节点删掉(也就是值为6的左孩子)

注意:这里还有个特树情况,就是删除值为8的根节点,方法也是一样的,这里不过多赘述

这里展示完整代码,已做实现区分,请看其中注释:

cpp 复制代码
bool Erase(const T& key)
    {
        Node* cur = _root;
        Node* parent = nullptr;

        // 1. 先找到要删除的节点
        while (cur != nullptr)
        {
            if (key < cur->_key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else if (key > cur->_key)
            {
                parent = cur;
                cur = cur->_right;
            }
            // 找到需要删除的节点 cur
            else
            {
                // 情况1:左子树为空
                if (cur->_left == nullptr)
                {
                    // 判断是否为根节点
                    if (cur == _root)
                        _root = cur->_right;
                    else if (parent->_left == cur)
                        parent->_left = cur->_right;
                    else
                        parent->_right = cur->_right;

                    delete cur;
                }
                // 情况2:右子树为空
                else if (cur->_right == nullptr)
                {
                    if (cur == _root)
                        _root = cur->_left;
                    else if (parent->_left == cur)
                        parent->_left = cur->_left;
                    else
                        parent->_right = cur->_left;

                    delete cur;
                }
                // 情况3:左右子树都不为空(最难)
                else
                {
                    // 找右子树最左节点 -> 中序后继
                    Node* minParent = cur;
                    Node* minRight = cur->_right;
                    while (minRight->_left != nullptr)
                    {
                        minParent = minRight;
                        minRight = minRight->_left;
                    }

                    // 覆盖替换要删除节点的值
                    cur->_key = minRight->_key;

                    // 删掉后继节点
                    if (minParent->_left == minRight)
                        minParent->_left = minRight->_right;
                    else
                        minParent->_right = minRight->_right;

                    delete minRight;
                }
                return true;
            }
        }
        // 没找到该值,删除失败
        return false;
    }

需要注意的是,为什么这里面没有把无孩子的节点分类出来,因为他们的删除逻辑已经包含在其中了

6.搜索二叉树其余接口的实现

6.1 中序遍历

可能有同学要问到为什么要用中序遍历, 因为二叉搜索树的中序遍历结果,必然是严格升序的序列

而中序遍历的顺序是:左子树 → 根节点 → 右子树

cpp 复制代码
// 模板 二叉搜索树类
template<class T>
class BSTree
{
    // 类型重定义,简化代码
    typedef BSTNode<T> Node;

private:
    Node* _root;  // 根节点

    // 中序遍历 子函数(递归)
    void _InOrder(Node* root)
    {
        if (root == nullptr)
            return;

        _InOrder(root->_left);
        cout << root->_key << " ";
        _InOrder(root->_right);
    }

这里的_InOrder函数封装在该类的内部为私有成员,这是一种封装的思想**,Node是树内部的结构,不应该暴露在外面**

cpp 复制代码
tree.InOrder(root);  // ❌ 错误,用户不应该接触节点
tree.InOrder();      // ✅ 正确,用户只需要调用

因此写成私有类后,外部无法直接调用,但是可以通过函数间接调用,因此我们把它封装起来

cpp 复制代码
public:
    void InOrder() {
        _InOrder(_root);  // 内部调用私有实现
    }

6.2 树的析构函数

和中序遍历一样,这里也需要封装成私有类,原因一样,不过多赘述,同时也用递归写

cpp 复制代码
~BSTree()
{
    Destroy(_root);
    _root = nullptr;
}
private:
void Destroy(Node* root)
{
    if (root == nullptr)
	    return;
    
    Destroy(root->_left);
    Destroy(root->_right);
    delete root;
}

6.3 树的拷贝函数

这里的 Copy 函数,用的是 前序遍历 顺序:根 → 左 → 右

作用:二叉搜索树深拷贝拷贝构造函数,用来克隆一整棵 BST,两棵树完全独立,互不影响

同样树的拷贝函数的实现也需要封装起来:

1. 拷贝构造函数

cpp 复制代码
BSTree(const BSTree& t)
{
    _root = Copy(t._root);
}
  • 这是拷贝构造 :用一个已存在的树 t,创建一个一模一样的新树
  • t._root:传入原树的根节点
  • 调用私有递归函数 Copy,递归复制整棵树,最终把新树根赋值给当前对象 _root

2. 递归拷贝核心函数

cpp 复制代码
private:
Node* Copy(Node* root)
{
	// 递归终止条件:空节点直接返回空
    if (root == nullptr)
        return nullptr;

    // 1. 先创建当前根节点 👉 前序第一步:根
    Node* newRoot = new Node(root->_key, root->_value);
    
    // 2. 递归拷贝左子树 👉 前序第二步:左
    newRoot->_left = Copy(root->_left);
    
    // 3. 递归拷贝右子树 👉 前序第三步:右
    newRoot->_right = Copy(root->_right);

    // 返回当前新节点,给上层接收
    return newRoot;
}

为什么这里必须是前序遍历呢?

  • 必须先造当前节点 你要先 new 出根节点,才能给它的 _left_right 赋值。
  • 如果换成中序 / 后序,逻辑有问题
    • 后序:左→右→根,最后才创建当前节点,没法提前挂载左右孩子
    • 中序:左→根→右,顺序错乱,树结构无法正确构建

6.4 树的默认构造

上面讲完树的拷贝构造这里就要讲一下树的默认构造,由于拷贝构造也是构造,所以编译器不会再自动生成默认构造,但我们又必须要默认构造,当我们创建一个新的树的时候,此时根节点为空,因此这里初始时需要一个默认构造来创建空树

cpp 复制代码
// 强制生成构造
		BSTree() = default;

这里就要用到C++11的强制生成默认构造了


完整代码实现如下:

cpp 复制代码
#pragma once
#include<iostream>
using namespace std;

namespace key_value
{
	template<class K, class V>
	struct BSTNode
	{
		K _key;
		V _value;

		BSTNode<K, V>* _left;
		BSTNode<K, V>* _right;

		BSTNode(const K& key, const V& value)
			:_key(key)
			, _value(value)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

	// Binary Search Tree
	// Key/value
	template<class K, class V>
	class BSTree
	{
		//typedef BSTNode<K> Node;
		using Node = BSTNode<K, V>;
	public:
		// 强制生成构造
		BSTree() = default;

		BSTree(const BSTree& t)
		{
			_root = Copy(t._root);
		}

		BSTree& operator=(BSTree tmp)
		{
			swap(_root, tmp._root);
			return *this;
		}

		~BSTree()
		{
			Destroy(_root);
			_root = nullptr;
		}

		bool Insert(const K& key, const V& value)
		{
			if (_root == nullptr)
			{
				_root = new Node(key, value);
				return true;
			}

			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
				{
					return false;
				}
			}

			cur = new Node(key, value);
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}

		Node* 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 cur;
				}
			}

			return nullptr;
		}

		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
				{
					// 删除
					// 左为空
					if (cur->_left == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}
						}
						delete cur;

					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							// 右为空
							if (parent->_left == cur)
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}

						delete cur;

					}
					else
					{
						// 左右都不为空
						// 右子树最左节点
						Node* replaceParent = cur;
						Node* replace = cur->_right;
						while (replace->_left)
						{
							replaceParent = replace;
							replace = replace->_left;
						}

						cur->_key = replace->_key;

						if (replaceParent->_left == replace)
							replaceParent->_left = replace->_right;
						else
							replaceParent->_right = replace->_right;

						delete replace;
					}

					return true;
				}
			}

			return false;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

			_InOrder(root->_left);
			cout << root->_key << ":" << root->_value << endl;
			_InOrder(root->_right);
		}

		void Destroy(Node* root)
		{
			if (root == nullptr)
				return;

			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
		}

		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;

			Node* newRoot = new Node(root->_key, root->_value);
			newRoot->_left = Copy(root->_left);
			newRoot->_right = Copy(root->_right);
			return newRoot;
		}
	private:
		Node* _root = nullptr;
	};
}

namespace key
{
	template<class K>
	struct BSTNode
	{
		K _key;
		BSTNode<K>* _left;
		BSTNode<K>* _right;

		BSTNode(const K& key)
			:_key(key)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

	// Binary Search Tree
	// Key
	template<class K>
	class BSTree
	{
		//typedef BSTNode<K> Node;
		using Node = BSTNode<K>;
	public:

		bool Insert(const K& key)
		{
			if (_root == nullptr)
			{
				_root = new Node(key);
				return true;
			}

			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
				{
					return false;
				}
			}

			cur = new Node(key);
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}

		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 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
				{
					// 删除
					// 左为空
					if (cur->_left == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}
						}
						delete cur;

					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							// 右为空
							if (parent->_left == cur)
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}

						delete cur;

					}
					else
					{
						// 左右都不为空
						// 右子树最左节点
						Node* replaceParent = cur;
						Node* replace = cur->_right;
						while (replace->_left)
						{
							replaceParent = replace;
							replace = replace->_left;
						}

						cur->_key = replace->_key;

						if (replaceParent->_left == replace)
							replaceParent->_left = replace->_right;
						else
							replaceParent->_right = replace->_right;

						delete replace;
					}

					return true;
				}
			}

			return false;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
	private:

		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}

	private:
		Node* _root = nullptr;
	};
}

搜索二叉树整体来讲难度不大,后面我们还会接触AVL树和红黑树,以及学习map和set等他们的底层都是红黑树,那这个就会更复杂一点

相关推荐
如君愿2 小时前
考研复习 Day 18 | 数据结构与算法--图(上)
数据结构·考研·记录考研
自我意识的多元宇宙2 小时前
【数据结构】平衡二叉树
数据结构
睡觉就不困鸭2 小时前
第十六天 反转字符串II
数据结构
测绘第一深情3 小时前
租用GPU云服务器进行深度学习(AutoDL,超保姆级,适用新手)
数据结构·人工智能·经验分享·python·深度学习·算法·计算机视觉
北顾笙9803 小时前
day34-数据结构力扣
数据结构·算法·leetcode
自我意识的多元宇宙3 小时前
【数据结构】二叉排序树
数据结构·算法
Epiphany.5563 小时前
树上dp问题
数据结构·算法
自我意识的多元宇宙3 小时前
【数据结构】 散列表
数据结构·散列表
承渊政道3 小时前
【动态规划算法】(简单多状态dp问题入门与经典题型解析)
数据结构·c++·学习·算法·leetcode·macos·动态规划