从零开始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等他们的底层都是红黑树,那这个就会更复杂一点

相关推荐
闪电悠米3 小时前
力扣hot100-41.缺失的第一个正数-原地哈希详解
数据结构·算法·哈希算法
WL学习笔记4 小时前
堆(数据结构)
数据结构·
wabs6667 小时前
关于图论【卡码网100.最大岛屿的面积的思考】
数据结构·算法·图论
yuannl109 小时前
图的最短路径Dijkstra
数据结构
zander25810 小时前
138. 随机链表的复制
数据结构·算法·链表
过期动态10 小时前
【LeetCode 热题 100】找到字符串中所有字母异位词
java·数据结构·算法·leetcode·职场和发展·rabbitmq
来一碗刘肉面11 小时前
栈在递归中的应用
数据结构·算法
皓月斯语11 小时前
程序设计语言的特点
开发语言·数据结构·c++
邪修king11 小时前
C++ 进阶终章:异常机制与智能指针全解 —— 从错误处理到 RAII 资源管理,打通现代 C++ 的核心命脉
android·数据结构·c++
Adios79419 小时前
设置交集大小至少为2
数据结构·算法·leetcode