探索数据结构:二叉搜索树的递归与非递归实现

news2025/4/6 4:45:52

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:数据结构与算法
贝蒂的主页:Betty’s blog

1. 二叉搜索树的介绍

二插入搜索树(Binary Search Tree,简称 BST),也叫做二叉排序树。它一种常见的运用于查找的数据结构。

它具有以下重要特点:

  1. 每个节点最多有两个子节点,分别称为左子节点和右子节点。
  2. 若左子树不为空,左子树中的所有节点的值都小于根节点的值。
  3. 若右子树不为空,右子树中的所有节点的值都大于根节点的值。
  4. 左右子树也都是二叉搜索树。

其中需要特别注意的是空树也是一颗二叉搜索树。而由于其性质,所以二叉搜索树在进行中序遍历就能对数据进行升序排序。

img

这种结构使得在其中进行查找、插入和删除操作具有较高的效率。例如,如果要查找一个特定的值,从根节点开始,如果要查找的值小于根节点的值,就向左子树查找;如果大于根节点的值,就向右子树查找。一直重复这个过程,直到找到目标值或者确定目标值不存在。

在插入操作时,也是通过比较要插入的值与当前节点的值,决定将其插入到左子树还是右子树。

删除操作则相对复杂一些,需要根据被删除节点的子节点情况进行不同的处理。

并且二叉入搜索树在很多领域都有广泛应用,比如数据库中的索引结构、文件系统的目录结构等。它能够有效地提高数据的存储和检索效率。

2. 二叉搜索树的功能

以下是二叉搜索树的常见功能:

  1. 二叉搜索树的插入。
  2. 二叉搜索树的查找。
  3. 二叉搜索树的删除。

3. 二叉搜索树的结构

3.1. 二叉搜索树的节点

二叉搜索树的节点本质与二叉树一样,所以有三个成员变量:左子树_left,右子树_right,键值_val。当然为了适配不同的类型,我们可以定义一个模版.。

template<class K>
struct BSTreeNode
{
    //构造函数
    BSTreeNode(const K&key)
        :_left(nullptr)
        ,_right(nullptr)
        ,_key(key)
    {
    }
    BSTreeNode<K>* _left;//左子树
    BSTreeNode<K>* _right;// 右子树
    K _key;// 键值
};

3.2. 二叉搜索树

而我们在定义二叉搜索树时就可以利用这个节点,并初始化为空。

template<class K>
class BSTree
{
    typedef BSTreeNode<K> Node;
public:
    //成员函数
private:
    //成员变量
    Node* _root = nullptr;
};

4. 二叉搜索树的初始化与销毁

4.1. 构造函数/拷贝构造/赋值重载

首先我们直接定义一个无参的构造函数,因为我们在定义拷贝构造之后编译器就不会在生成默认的构造函数了。

BSTree()
{}

之后我们可以利用递归来实现一个拷贝构造函数。

BSTree(const BSTree& t)
{
    _root = copy(t._root);
}
Node* copy(Node* root)
{
    if (root == nullptr)
        return nullptr;
    Node* newnode = new Node(root->_key);
    newnode->_left = copy(root->_left);
    newnode->_right = copy(root->_right);
    return root;
}

最后我们通过一个简单的方式实现赋值重载——通过形参调用拷贝构造出一个临时变量,然后交换this所指向的变量,这样原本this所指向的对象出了作用域就会销毁,间接实现了实现赋值重载。

BSTree<K>& operator=(const BSTree<K> t)
{
    //赋值重载
    this->swap(_root, t._root);
    return *this;
}

4.2. 析构函数

析构函数需要借助递归释放所有节点,而为了方便我们传参我们可以定义子函数来帮助我们解决。

~BSTree()
{
    Destory(_root);
}
void Destroy(Node*&root)
{
    if (root == nullptr)
    {
        return;
    }
    Destroy(root->_left);
    Destroy(root->_right);
    delete root;
    root = nullptr;
}

5. 二叉搜索树的插入

二叉搜索树的插入十分简单,从根节点开始,如果要查找的值小于根节点的值,就向左子树查找;如果大于根节点的值,就向右子树查找,一直重复这个过程。

在查找过程中可能出现两种情况:

  1. 查找到空节点即要插入位置,插入成功返回true
  2. 查找到相同值的节点,插入失败返回false

img

img

5.1. 非递归实现

非递归实现首先得保存父节点parent,方便之后插入新的节点。然后循环查找插入位置,查找失败就返回false,查找成功则判断该插入parent的左边还是右边。

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
		{
			//相同值返回false
			return false;
		}
	}
	//插入
	cur = new Node(key);
    //判断该插入哪边
	if (key < parent->_key)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	return true;
}

5.2. 递归实现

递归实现的大逻辑与非递归实现时一样的,当key<root->_key就往左子树插入,当key > root->_key就往左子树插入。其中需要注意的是为了传参方便我们定义一个子函数来实现递归,并且利用引用传参减少拷贝并直接对原二叉搜索树进行修改。

bool InsertR(const K& key)
{
    return _InsertR(_root, key);
}
bool _InsertR(Node* &root, const K& key)
{
    //插入
    if (root == nullptr)
    {
        root = new Node(key);
        return true;
    }
    if (key < root->_key)
    {
        return _InsertR(root->_left, key);
    }
    else if (key > root->_key)
    {
        return _InsertR(root->_right, key);
    }
    else
    {
        //相同返回false
        return false;
    }
}

6. 二叉搜索树的查找

二叉搜索树的查找也十分简单,从根节点开始,如果要查找的值小于根节点的值,就向左子树查找;如果大于根节点的值,就向右子树查找,一直重复这个过程。

在查找过程中可能出现两种情况:

  1. 查找到空节点,查找失败返回false
  2. 查找到相同值的节点,查找成功返回true

img

img

6.1. 非递归实现

仔细观察我们就发现查找的逻辑其实就是插入的子逻辑,因为插入的前提就是查找,所以实现也更简单。

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;
}

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
    {
        return true;//找到了
    }
}

7. 二叉搜索树的删除

二叉搜索树的删除的逻辑是最麻烦的,首先我们定义一个parent节点方便之后链接。然后通过具体情况分析:

主要可以分为以下几种情况:

  1. 删除节点的左子树为空,位于父节点的右子树。

img

  1. 删除节点的左子树为空,位于父节点的左子树。

img

  1. 删除节点的右子树为空,位于父节点的右子树。

img

  1. 删除节点的右子树为空,位于父节点的左子树。

img

  1. 删除节点为根节点,并且左或右子树为空。

imgimg

  1. 删除节点左右子树都不为空。

当删除节点左右子树都不为空时,需采用伪删除法。即寻找到左子树的最右节点即左子树的最大值,或者是右子树的最左节点即右子树的最小值。然后赋值,最后转换为在左子树或者右子树删除节点。

img

当然删除节点时需要判断,其位于父节点的左子树还是右子树。

img

7.1. 非递归实现

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
        {
                
            //1.左为空
            if (cur->_left == nullptr)
            {
                //判断是否删除的是根节点
                if (cur == _root)
                {
                    _root = cur->_right;
                }
                else
                {
                //判断cur为parent左子树还是右子树
                    if (cur == parent->_left)
                        parent->_left = cur->_right;
                    else
                        parent->_right = cur->_right;
                }
                delete cur;
                cur = nullptr;
            }
            //2.右为空
            else if (cur->_right == nullptr)
            {
                //判断是不是为根节点
                if (cur == _root)
                {
                    _root = cur->_left;
                }
                //判断cur为parent左子树还是右子树
                else
                {
                    if (cur == parent->_left)
                        parent->_left = cur->_left;
                    else
                        parent->_right = cur->_left;
                }
                delete cur;
                cur = nullptr;
            }
            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;
                minRight = nullptr;
            }
            return true;
        }
    }
    //找不到直接返回false
    return false;
}

7.2. 递归实现

递归实现采用引用传参,这时root就是parent左子树或者右子树的地址。当删除节点的左子树为空时,我们直接将右子树赋值给root,如果右子树为空,我们直接将左子树赋给root。当左右子树都不为空时,我们可以采用伪删除法将问题转换为在左子树或右子树删除某个叶子节点,从而实现递归调用。当然我们需要提前保存要删除节点,防止更新之后找不到。

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;
    }
}

8. 判断是否为二叉搜索树

为了验证我们最后生成的二叉树是二叉搜索树,我们无法通过肉眼观察,所以最好直接写一个函数判断。

通过二叉搜索树的定义,我们可以设计一个递归函数 _isValidBST(root, lower, upper) 来递归判断,函数表示考虑以 root为根的子树,判断子树中所有节点的值是否都在 (lower,upper) 的开区间范围内,即是否都小于左节点,大于右节点。如果 root节点的值_key 不在(lower,upper) 的范围内说明不满足条件直接返回,否则我们要继续递归调用检查它的左右子树是否满足,如果都满足才说明这是一棵二叉搜索树。

bool isValidBST(Node* root)
{
    return _isValidBST(root, LONG_MIN, LONG_MAX);
}
bool _isValidBST(Node* root, long long lower, long long upper)
{
    if (root == nullptr)
    {
        return true;
    }
    if (root->_key <= lower || root->_key >= upper)
    {
        return false;
    }
    bool left = _isValidBST(root->_left, lower, root->_key);
    bool right = _isValidBST(root->_right, root->_key, upper);
    return left && right;
}

9. 二叉搜索树的性能分析

二叉搜索树的插入与删除本质都先查找,所以查找代表了二叉搜索树的操作的性能。当二叉搜索树接近完全二叉树时,时间复杂度就为O(logN)。

img

但是当二叉搜索树接近有序插入时,二叉搜索树就会退化为单链表,这是时间复杂度为O(N)。

img

而二叉搜索树的各个操作都不会开辟大量空间,所以空间复杂度为O(1)。

10. 二叉搜索树的应用

10.1. K模型

K模型即只有_key作为关键码,结构中只存储Key,关键码即为需要搜索的值。比如需要判断某个单词是否拼写正确,首先我们将词库中所有单词集合里的每个单词作为关键码(key)来构建一棵二叉搜索树。然后,在这棵二叉搜索树中检索给定的单词是否存在。如果存在,就表明该单词拼写正确;如果不存在,就表明拼写错误。

我们上述实现的二叉搜索树就是基于K模型实现的。

10.2. KV模型

KV模型即每一个关键码_key,都有与之对应的值_Value,即结构中存储<Key, Value>的键值对。比如需要通过电话号码找到对应的联系人,这时就可以存储<tele_num,name>的键值对。然后将所有集合构建一颗二叉搜索树,之后就能在这课二叉搜索树中通过电话号码查找对应的联系人。

而实现KV模型也十分简单,只需要将节点BSTreeNode增加一个参数val,引入第二次模版参数V,然后根据实际情况修改一下即可。

11. 源码

11.1. K模型

namespace K
{
	template<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:
		//成员函数
		BSTree()
		{}
		BSTree(const BSTree& t)
		{
			_root = copy(t._root);
		}
		BSTree<K>& operator=(const BSTree<K> t)
		{
			//赋值重载
			swap(_root, t._root);
			return *this;
		}
		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
				{
					//相同值返回false
					return false;
				}
			}
			//插入
			cur = new Node(key);
			if (key < parent->_key)
			{
				//左子树
				parent->_left = cur;
			}
			else
			{
				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
				{

					//1.左为空
					if (cur->_left == nullptr)
					{
						//判断是否删除的是根节点
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							//判断cur为parent左子树还是右子树
							if (cur == parent->_left)
								parent->_left = cur->_right;
							else
								parent->_right = cur->_right;
						}
						delete cur;
						cur = nullptr;
					}
					//2.右为空
					else if (cur->_right == nullptr)
					{
						//判断是不是为根节点
						if (cur == _root)
						{
							_root = cur->_left;
						}
						//判断cur为parent左子树还是右子树
						else
						{
							if (cur == parent->_left)
								parent->_left = cur->_left;
							else
								parent->_right = cur->_left;
						}
						delete cur;
						cur = nullptr;
					}
					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;
						minRight = nullptr;
					}
					return true;
				}
			}
			//找不到直接返回false
			return false;
		}

		bool isValidBST(Node* root)
		{
			return _isValidBST(root, LONG_MIN, LONG_MAX);
		}

		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()
		{
			Destroy(_root);
		}
	private:
		bool _isValidBST(Node* root, long long lower, long long upper)
		{
			if (root == nullptr)
			{
				return true;
			}
			if (root->_key <= lower || root->_key >= upper)
			{
				return false;
			}
			bool left = _isValidBST(root->_left, lower, root->_key);
			bool right = _isValidBST(root->_right, root->_key, upper);
			return left && right;
		}
		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->_left, key);
			}
			else if (key > root->_key)
			{
				return _InsertR(root->_right, key);
			}
			else
			{
				//相同返回false
				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;//找到了
			}
		}
		Node* copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;
			Node* newnode = new Node(root->_key);
			newnode->_left = copy(root->_left);
			newnode->_right = copy(root->_right);
			return newnode;
		}
		void Destroy(Node*& root)
		{
			if (root == nullptr)
			{
				return;
			}
			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
			root = nullptr;
		}
		//成员变量
		Node* _root = nullptr;
	};
}

11.2. KV模型

namespace KV
{
	template<class K,class V>
	struct BSTreeNode
	{
		//构造函数
		BSTreeNode(const K& key,const V&val)
			:_left(nullptr)
			, _right(nullptr)
			, _key(key)
			,_val(val)
		{
		}
		BSTreeNode<K,V>* _left;//左子树
		BSTreeNode<K,V>* _right;// 右子树
		K _key;// 键值
		V _val;
	};
	template<class K,class V>
	class BSTree
	{
		typedef BSTreeNode<K,V> Node;
	public:
		//成员函数
		BSTree()
		{}
		BSTree(const BSTree<K,V>& t)
		{
			_root = copy(t._root);
		}
		BSTree<K,V>& operator=(const BSTree<K,V> t)
		{
			//赋值重载
			swap(_root, t._root);
			return *this;
		}
		bool Insert(const K& key,const V&val)
		{
			if (_root == nullptr)
			{
				_root = new Node(key,val);
				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
				{
					//相同值返回false
					return false;
				}
			}
			//插入
			cur = new Node(key,val);
			if (key < parent->_key)
			{
				//左子树
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}
			return true;
		}
		Node* 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 cur;
				}
			}
			//找到空
			return nullptr;
		}
		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
				{

					//1.左为空
					if (cur->_left == nullptr)
					{
						//判断是否删除的是根节点
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							//判断cur为parent左子树还是右子树
							if (cur == parent->_left)
								parent->_left = cur->_right;
							else
								parent->_right = cur->_right;
						}
						delete cur;
						cur = nullptr;
					}
					//2.右为空
					else if (cur->_right == nullptr)
					{
						//判断是不是为根节点
						if (cur == _root)
						{
							_root = cur->_left;
						}
						//判断cur为parent左子树还是右子树
						else
						{
							if (cur == parent->_left)
								parent->_left = cur->_left;
							else
								parent->_right = cur->_left;
						}
						delete cur;
						cur = nullptr;
					}
					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;
						minRight = nullptr;
					}
					return true;
				}
			}
			//找不到直接返回false
			return false;
		}

		bool isValidBST(Node* root)
		{
			return _isValidBST(root, LONG_MIN, LONG_MAX);
		}

		Node* FindR(const K& key)
		{
			return _FindR(_root, key);
		}
		bool InsertR(const K& key,const V&val)
		{
			return _InsertR(_root, key, val);
		}
		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}
		~BSTree()
		{
			Destroy(_root);
		}
	private:
		bool _isValidBST(Node* root, long long lower, long long upper)
		{
			if (root == nullptr)
			{
				return true;
			}
			if (root->_key <= lower || root->_key >= upper)
			{
				return false;
			}
			bool left = helper(root->_left, lower, root->_key);
			bool right = helper(root->_right, root->_key, upper);
			return left && right;
		}
		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, const V&val)
		{
			//插入
			if (root == nullptr)
			{
				root = new Node(key,val);
				return true;
			}
			if (key < root->_key)
			{
				return _InsertR(root->_left, key, val);
			}
			else if (key > root->_key)
			{
				return _InsertR(root->_right, key, val);
			}
			else
			{
				//相同返回false
				return false;
			}

		}
		Node*_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 root;//找到了
			}
		}
		Node* copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;
			Node* newnode = new Node(root->_key,root->_val);
			newnode->_left = copy(root->_left);
			newnode->_right = copy(root->_right);
			return newnode;
		}
		void Destroy(Node*& root)
		{
			if (root == nullptr)
			{
				return;
			}
			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
			root = nullptr;
		}
		//成员变量
		Node* _root = nullptr;
	};
}
;
				}
				delete del;
				return true;
			}
		}
		bool _InsertR(Node*& root, const K& key, const V&val)
		{
			//插入
			if (root == nullptr)
			{
				root = new Node(key,val);
				return true;
			}
			if (key < root->_key)
			{
				return _InsertR(root->_left, key, val);
			}
			else if (key > root->_key)
			{
				return _InsertR(root->_right, key, val);
			}
			else
			{
				//相同返回false
				return false;
			}

		}
		Node*_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 root;//找到了
			}
		}
		Node* copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;
			Node* newnode = new Node(root->_key,root->_val);
			newnode->_left = copy(root->_left);
			newnode->_right = copy(root->_right);
			return newnode;
		}
		void Destroy(Node*& root)
		{
			if (root == nullptr)
			{
				return;
			}
			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
			root = nullptr;
		}
		//成员变量
		Node* _root = nullptr;
	};
}

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

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

相关文章

第16课 Scratch入门篇:师生问候-广播版

师生问候-广播版 故事背景&#xff1a; 上节课我们完成了师生问候功能&#xff0c;指令罗列的蛮多&#xff0c;写起来很麻烦&#xff0c;而且阅读起来不容易理解&#xff0c;这节课我们把上节课内容进行优化&#xff0c;引入一个新的指令-广播&#xff0c;广播相当于一个事件的…

DFS之迭代加深+双向DFS+IDA*

迭代加深&#xff1a; 搜索范围一层一层扩大&#xff0c;可以快速某些分支比较深&#xff0c;但是答案比较浅的问题。 https://www.acwing.com/problem/content/172/ 通过观察可以发现&#xff1a; 1.搜索时最坏情况可能搜到100层&#xff0c;比较深&#xff0c;但是答案应…

【Javax.Validation】✈️整合 SpringBoot 实现运行时的参数校验

目录 &#x1f44b;前言 &#x1f440;一、Validation 依赖 &#x1f4eb;二、常见注解 2.1 不需要指定注解参数 2.2 需要声明注解参数 &#x1f49e;️三、项目测试注解使用 3.1 依赖引入 3.2 实体类创建 3.3 创建对外接口 3.4 模拟请求 &#x1f331;四、章末 &#x1f…

RSS 2024 清华大学交叉院高阳提出高效的机器人操作技能学习方法

机器人掌握一项新技能需要几步&#xff1f; 一般来说&#xff0c;在传统机器学习方法中&#xff0c;通常使用演示学习的方式教会机器人掌握新技能&#xff0c;然而&#xff0c;收集高质量的演示数据成本高且耗时&#xff0c;一定程度上影响了机器人技能学习进度。尽管视频作为…

⭕️【论文阅读】《Interactive Class-Agnostic Object Counting》

[2309.05277] Interactive Class-Agnostic Object Counting (arxiv.org) code&#xff1a; cvlab-stonybrook/ICACount: [ICCV23] Official Pytorch Implementation of Interactive Class-Agnostic Object Counting (github.com) 目录 Abstract Abstract 我们提出了一个新…

linux学习记录(一)--------目录及文件操作

文章目录 前言Linux目录及文件操作1.Linux目录结构2.常用的Linux命令3.vi编辑器的简单使用4.vi的两个模式 前言 小白学习linux记录有错误随时指出~ Linux目录及文件操作 Linux采用Shell命令->操作文件 1.Linux目录结构 根目录&#xff1a;/ 用户目录&#xff1a;~或者/ho…

float转uint8_t数组

float类型在x64中占4字节&#xff0c;需要占据uint8_t数组大小4字节 数据float类型3.14&#xff0c;在内存中4字节地址应该为0x4048f5c3 如果直接使用memcpy内存复制&#xff0c;0xc3会放在数组下标小的位置

〖任务1〗ROS2 jazzy Linux Mint 22 安装教程

前言&#xff1a; 本教程在Linux系统上使用。 目录 一、linux安装二、linux VPN安装三、linux anaconda安装&#xff08;可选&#xff09;四、linux ROS2 安装五、rosdep init/update 解决方法六、安装GUI 一、linux安装 移动硬盘安装linux&#xff1a;[LinuxToGo教程]把ubunt…

代码随想录算法训练营第43天|LeetCode 300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组

1. LeetCode 300.最长递增子序列 题目链接&#xff1a;https://leetcode.cn/problems/longest-increasing-subsequence/description/ 文章链接&#xff1a;https://programmercarl.com/0300.最长上升子序列.html 视频链接&#xff1a;https://www.bilibili.com/video/BV1ng411J…

进阶SpringBoot之 Spring 官网或 IDEA 快速构建项目

SpringBoot 就是一个 JavaWeb 的开发框架&#xff0c;约定大于配置 程序 数据结构 算法 微服务架构是把每个功能元素独立出来&#xff0c;再动态组合&#xff0c;是对功能元素的复制 这样做可以节省调用资源&#xff0c;每个功能元素的服务都是一个可替代、可独立升级的软…

从地铁客流讲开来:超一线城市的客运量特征

这篇我们把视角聚焦在四大超一线城市&#xff0c;北上广深&#xff0c;我们来看看这些城市地铁客运量的异同&#xff0c;这里放一个背景2024年6月8日—6月10日是我国农历的端午节&#xff0c;我们看图说话&#xff0c;相同的特征&#xff1a;1.四大一线城市客流都在周五达到客运…

数学建模--蒙特卡罗随机模拟

目录 蒙特卡罗方法的基本原理 蒙特卡罗方法在优化中的应用 蒙特卡罗方法的优势与局限 优势 局限 典型应用案例 Python代码示例 ​编辑 结论 蒙特卡罗方法在数学建模中的具体应用案例有哪些&#xff1f; 如何改进蒙特卡罗方法以提高计算效率和精度&#xff1f; 蒙特…

如何使你的mermaid流程图里的某一段文字加粗、变斜、成为上下标……

目录 参考的链接开头1.加粗&#xff0c;*斜体*与下划线2.标记,~~删除线~~与^上^~下~标3.代码片与标题4.注释与蓝色链接5.其</q>他 东西 结尾 参考的链接 HTML标签列表(按字母排序)和HTML标签列表(按功能排序) 开头 大家好&#xff0c;我叫这是我58。今天&#xff0c;我们…

【架构】客户端优化

这篇文章总结一下服务器网关及之前部分的优化&#xff0c;如客户端的优化&#xff0c;CDN/DNS等。 这里我们先谈一谈客户端缓存优化的手段。一般我们后端在说到缓存&#xff0c;第一时间想到的往往是redis&#xff0c;其实缓存在架构层次还有很多其他可以实现的地方&#xff0…

从LLM到大模型推理的最新进展

大语言模型LLM的推理引擎经过一年时间发展&#xff0c;现在主流方案收敛到了开源的vLLM和半闭源的TensorRT-LLM。 TRT-LLM基于C开发&#xff0c;有NV算子开发黑魔法加持&#xff0c;在其重点支持的场景里&#xff0c;性能可以做到极致。vLLM基于python开发&#xff0c;代码简洁…

MySQL第2讲--关系型数据库以及SQL语句分类之DDL数据库和表的操作

文章目录 前言关系型数据库&#xff08;RDBMS&#xff09;关系型数据库的特点 MySQL数据模型SQL介绍基本语法规则SQL语句的分类DDL的介绍DDL的数据库操作DDL的表操作 前言 上一节MySQL第1讲–详细安装教程和启动方法中介绍了MySQL如何安装&#xff0c;以及如何启动和客户端连接…

使用 Elastic 和 Mistral 构建多语言 RAG

作者&#xff1a;来自 Elastic Gustavo Llermaly 使用 Elastic 和 Mixtral 8x22B 模型构建多语言 RAG 应用程序。 Mixtral 8x22B 是性能最高的开放式模型&#xff0c;其最强大的功能之一是能够流利使用多种语言&#xff1b;包括英语、西班牙语、法语、意大利语和德语。 想象一…

质量属性-系统架构师(四十)

质量属性 1性能&#xff1a; 指系统响应能力。如响应时间、吞吐量。 设计策略&#xff1a;优先级队列&#xff0c;增加计算机资源&#xff0c;减少计算机开销&#xff0c;引入并发机制&#xff0c;采用资源调度。 2可靠性&#xff1a; 在一定时间内正常运行的情况下&#x…

PTA—基础编程题目集(7-21)

7-21 求特殊方程的正整数解 目录 题目描述 输入格式&#xff1a; 输出格式&#xff1a; 输入样例1&#xff1a; 输出样例1&#xff1a; 输入样例2&#xff1a; 输出样例2&#xff1a; 参考代码 总结 题目描述 本题要求对任意给定的正整数N&#xff0c;求方程X2Y2N的…

《Milvus Cloud向量数据库指南》——关于Ivy.ai:重塑沟通效率与数据安全的创新先锋

关于Ivy.ai:重塑沟通效率与数据安全的创新先锋 在数字化转型的浪潮中,Ivy.ai以其前瞻性的视野和专业团队的匠心独运,正逐步成为高等教育、医疗保健及公共部门沟通效率提升的引领者。这家企业不仅代表了人工智能技术在服务领域的最新进展,更以其旗舰产品IvyQuantum™的横空…