C++ | 二叉搜索树

news2024/11/13 12:04:54

前言

本篇博客讲解c++中的继承

💓 个人主页:普通young man-CSDN博客

⏩ 文章专栏:C++_普通young man的博客-CSDN博客

⏩ 本人giee:   普通小青年 (pu-tong-young-man) - Gitee.com

      若有问题 评论区见📝

🎉欢迎大家点赞👍收藏⭐文章
————————————————


目录

  二叉搜索树的概念

二叉搜索树的性能分析

最优情况

最差情况

平均情况

二分查找对比

平衡二叉搜索树

二叉搜索树的插入

二叉搜索树的查找

二叉搜索树的删除

二叉搜索树的实现代码

BSTNode 类

BSTree 类

内部细节

辅助函数

二叉搜索树key使用场景

场景1:小区无人值守车库

场景2:英文文章单词拼写检查

二叉搜索树key和key/value使用场景

场景1:简单的中英互译字典

场景2:商场无人值守车库

场景3:统计文章中单词出现的次数

key/value代码实现

BSTNode 类

BSTree 类

内部细节


二叉搜索树的概念

        二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树数据结构,它满足特定的排序属性,这使得在树中的查找、插入和删除操作变得高效。以下是二叉搜索树的一些关键概念和性质:

  1. 定义

    • 二叉搜索树是一种二叉树,每个节点最多有两个子节点,并且每个节点的值大于其左子树上的任何其他节点的值,小于其右子树上的任何其他节点的值。
    • 这意味着对于任意节点 n,所有左子树中的节点值 x 都满足 x ≤ n,所有右子树中的节点值 y 都满足 y ≥ n
  2. 性质

    • 左子树上所有结点的值均小于或等于根结点的值。
    • 右子树上所有结点的值均大于或等于根结点的值。
    • 根结点的左子树和右子树本身也必须是二叉搜索树。
    • 二叉搜索树中可以支持插入重复的键值,这取决于具体的应用场景。在一些实现中,如标准模板库(STL)中的 std::set 和 std::map 不允许重复键值;而 std::multiset 和 std::multimap 则允许重复键值。
  3. 操作

    • 查找:给定一个键值,在二叉搜索树中查找该键值对应的节点。
    • 插入:将一个新的节点添加到树中,同时保持二叉搜索树的性质。
    • 删除:从树中移除一个节点,并调整树以维持其为二叉搜索树。
    • 中序遍历:按照左-根-右的顺序访问树中的节点,可以得到一个递增的有序序列。

代码结构:

// 节点结构定义
template<class T>
class BSTNode {
public:
    T _key; // 节点存储的键值
    BSTNode<T>* _left; // 左子节点指针
    BSTNode<T>* _right; // 右子节点指针

    // 构造函数
    BSTNode(const T& key) :
        _key(key), // 初始化键值
        _left(nullptr), // 初始化左子节点指针为nullptr
        _right(nullptr) // 初始化右子节点指针为nullptr
    {}
};

二叉搜索树的性能分析

二叉搜索树(BST)的性能主要依赖于树的高度。下面是对不同情况下的性能分析:

最优情况

  • 当二叉搜索树是一棵完全二叉树(或者接近完全二叉树)时,树的高度是最小的,此时高度大约为 log⁡2N,其中 N 是树中的节点数量。
  • 在这种情况下,二叉搜索树的所有基本操作(查找、插入、删除)的时间复杂度都是 O(log⁡2N),这是非常高效的。

最差情况

  • 当二叉搜索树退化成一条链(单支树),即所有的节点只有一个子节点时,树的高度最大,此时高度为 N。
  • 在这种情况下,二叉搜索树的操作时间复杂度退化为 O(N),这意味着查找、插入、删除可能需要遍历整个树。

平均情况

  • 如果二叉搜索树的构建是随机的,并且没有明显的偏向性,那么平均情况下树的高度大致为 log⁡2N,因此平均时间复杂度为 O(log⁡2N)。

二分查找对比

  • 二分查找可以在 O(log⁡2N) 时间内完成查找,但前提是数据必须存储在一个支持随机访问的数据结构(如数组)中,并且数据必须是有序的。
  • 二分查找的缺点在于,对于插入和删除操作,由于数据存储在数组中,可能需要移动大量的元素来保持有序性,导致效率较低,通常是 O(N)。

平衡二叉搜索树

  • 为了解决二叉搜索树在最坏情况下的性能问题,引入了自平衡二叉搜索树的概念,如AVL树和红黑树。(这个后面我也会学习)
  • 这些树通过旋转等方法自动保持树的平衡,确保任何操作都不会使树的高度超过 O(log⁡2N),从而提供了更稳定的性能。

二叉搜索树的插入

  1. 树为空的情况

    • 如果二叉搜索树为空,那么直接创建一个新的节点,并将这个新节点作为树的根节点(root指针指向这个新节点)。
  2. 树不为空的情况

    • 如果二叉搜索树不为空,则根据二叉搜索树的性质进行插入操作:
      • 比较待插入值与当前节点的值:
        • 如果待插入值小于当前节点的值,则向当前节点的左子树方向移动。
        • 如果待插入值大于当前节点的值,则向当前节点的右子树方向移动。
      • 继续沿着左子树或右子树向下寻找,直到找到一个空的位置(即当前节点的左子节点或右子节点为空)。
      • 在找到的空位置处插入新的节点。
  3. 处理相等值的情况

    • 如果二叉搜索树支持插入相等的值,那么当待插入值等于当前节点的值时,可以选择向左子树方向移动或向右子树方向移动,找到空位置后插入新节点。
    • 注意保持逻辑的一致性,即如果选择向左插入相等的值,那么应该始终如此,或者始终选择向右插入,避免在不同的情况下选择不同的方向,这样可以避免混乱并保持树的一致性和可预测性。

代码展示:

bool insert(const T& key) {
    // 判断根节点是否为空
    if (_root == nullptr) {
        // 如果树为空,则创建一个新的节点作为根节点
        _root = new Node(key);
        return true; // 插入成功
    }

    // 使用两个指针,person 用来记录最近访问过的父节点,cur 用来记录当前正在访问的节点
    Node* person = nullptr;
    Node* cur = _root;

    // 循环遍历树直到找到空位置
    while (cur != nullptr) {
        if (key >= cur->_key) { // 如果待插入的键值大于等于当前节点的键值
            person = cur; // 更新父节点指针
            cur = cur->_right; // 向右子树移动
        }
        else if (key < cur->_key) { // 如果待插入的键值小于当前节点的键值
            person = cur; // 更新父节点指针
            cur = cur->_left; // 向左子树移动
        }
        else { // 如果键值已经存在,则返回失败
            return false;
        }
    }

    // 创建新的节点
    cur = new Node(key);

    // 将新节点链接到适当的位置
    if (key >= person->_key) { // 如果键值大于等于父节点的键值,则作为右子节点插入
        person->_right = cur;
    } else { // 否则作为左子节点插入
        person->_left = cur;
    }

    return true; // 插入成功
}

可以用这组数据测试:

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
	key::BSTree<int> s1;
	//插入
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	for (auto it : a)
	{
		s1.insert(it);
	}
	s1.InOrder();//

    s1.InOrder();->中序打印,是有序的,大家可以自己画画递归展开图,方便自己理解

 void _InOrder(BSTNode<T>* _root) {
        if (_root == nullptr) {
            return;
        }
        _InOrder(_root->_left); // 遍历左子树
        cout << _root->_key << " "; // 访问当前节点
        _InOrder(_root->_right); // 遍历右子树
    }



二叉搜索树的查找

查找操作的具体过程

  1. 从根开始比较

    • 从根节点开始,比较查找值 x 与当前节点的值。
    • 如果 x 大于当前节点的值,则向当前节点的右子树方向移动。
    • 如果 x 小于当前节点的值,则向当前节点的左子树方向移动。
  2. 最多查找高度次

    • 查找操作最多需要进行树的高度次比较。
    • 如果一直走到某个节点的左子节点或右子节点为空,还没有找到目标值 x,则说明该值不在树中。
  3. 不支持插入相等的值

    • 如果二叉搜索树不支持插入相等的值,那么一旦找到目标值 x 即可立即返回成功。
  4. 支持插入相等的值

    • 如果二叉搜索树支持插入相等的值,这意味着可能存在多个值为 x 的节点。
    • 在这种情况下,一般要求查找中序遍历的第一个值为 x 的节点。
    • 例如,在查找值 3 时,如果有多于一个 3,则应返回第一个在中序遍历中出现的 3

代码展示:

// 查找操作
bool find(const T& key) {
    Node* cur = _root; // 从根节点开始查找

    // 循环直到找到节点或到达空节点
    while (cur != nullptr) {
        if (key > cur->_key) { // 如果查找的键值大于当前节点的键值
            cur = cur->_right; // 向右子树移动
        } else if (key < cur->_key) { // 如果查找的键值小于当前节点的键值
            cur = cur->_left; // 向左子树移动
        } else { // 如果找到目标键值
            return true; // 找到目标键值,返回 true
        }
    }

    // 没有找到目标键值
    return false;
}

 测试代码:

	//查找
	if (s1.find(7))
	{
		cout << "OK"<<endl;
	}
	else
	{
		cout << "NO" << endl;
	}

二叉搜索树的删除

        删除二叉搜索树中的节点是一个稍微复杂的过程,因为它需要保持树的性质——对于任何节点,其所有左子树的节点的键值小于该节点的键值,所有右子树的节点的键值大于该节点的键值。下面是针对不同情况的解决方案:

情况总结表:

情况描述解决方案
1N的左右孩子均为空直接将N的父亲节点对应的子节点指针设为空,然后删除N节点。(也可以当作情况2或3处理)
2N的左孩子为空,右孩子不为空将N的父亲节点对应的子节点指针指向N的右孩子,然后删除N节点。
3N的右孩子为空,左孩子不为空将N的父亲节点对应的子节点指针指向N的左孩子,然后删除N节点。
4N的左右孩子均不为空使用N的左子树中的最大值节点R(最右节点)或N的右子树中的最小值节点R(最左节点)来替代N节点。替换后,转而删除R节点,此时R节点符合情况2或3。

文字描述:

  1. N的左右孩子均为空

    • 这是最简单的情况,只需要将N的父亲节点的相应孩子指针设为空,然后删除N即可。这种情况下,N没有子节点,因此不会影响树的结构。

  1. N的左孩子为空,右孩子不为空

    • 这种情况下,可以用N的右孩子来代替N的位置。具体操作是将N的父亲节点的相应孩子指针指向N的右孩子,然后删除N节点。
  2. N的右孩子为空,左孩子不为空

    • 类似于情况2,只是这次使用N的左孩子来代替N的位置。具体操作是将N的父亲节点的相应孩子指针指向N的左孩子,然后删除N节点。

  1. N的左右孩子均不为空

    • 这种情况比较复杂,不能直接删除N。可以找到N的左子树中的最大值节点R(即最右侧的节点)或N的右子树中的最小值节点R(即最左侧的节点),并将R的值与N交换。之后,问题转化为删除R节点,这时R节点只可能有一个孩子或者没有孩子,属于情况2或3。

这边一定是去找右子树的最左边的值,才是最合适的值

这里就可以发现为什么replacePerson为什么不给nullptr,而是要给cur,当我们要删除root就会野指针


代码实现:

bool erase(const T& key) {
    Node* person = nullptr; // person用于记录cur的父节点,以便在删除节点时调整父节点的指向
    Node* cur = _root; // 从根节点开始搜索目标键值对应的节点
    
    // 循环查找要删除的节点
    while (cur) {
        if (key > cur->_key) { // 如果目标键值大于当前节点键值,则向右子树搜索
            person = cur; // 更新person为当前节点,因为下一步将进入右子树
            cur = cur->_right; // 向右子树继续查找
        }
        else if (key < cur->_key) { // 如果目标键值小于当前节点键值,则向左子树搜索
            person = cur; // 更新person为当前节点,因为下一步将进入左子树
            cur = cur->_left; // 向左子树继续查找
        }
        else { // 找到了要删除的节点
            // 情况1: 要删除的是叶子节点或只有一个孩子的节点
            if (cur->_left == nullptr) { // 没有左孩子
                // 判断是否是根节点
                if (cur == _root) {
                    _root = cur->_right; // 将根节点设置为其右孩子
                }
                else { // 不是根节点
                    if (person->_left == cur) { // 要删除的节点是其父节点的左孩子
                        person->_left = cur->_right; // 将其父节点的左孩子设置为要删除节点的右孩子
                    }
                    else { // 要删除的节点是其父节点的右孩子
                        person->_right = cur->_right; // 将其父节点的右孩子设置为要删除节点的右孩子
                    }
                }
                delete cur; // 删除该节点
            }
            else if (cur->_right == nullptr) { // 没有右孩子
                // 判断是否是根节点
                if (cur == _root) {
                    _root = cur->_left; // 将根节点设置为其左孩子
                }
                else { // 不是根节点
                    if (person->_left == cur) { // 要删除的节点是其父节点的左孩子
                        person->_left = cur->_left; // 将其父节点的左孩子设置为要删除节点的左孩子
                    }
                    else { // 要删除的节点是其父节点的右孩子
                        person->_right = cur->_left; // 将其父节点的右孩子设置为要删除节点的左孩子
                    }
                }
                delete cur; // 删除该节点
            }
            // 情况2: 要删除的节点有两个孩子(左右孩子都有)
            else {
                Node* replacePerson = cur; // replacePerson记录待替换节点的父节点
                Node* replace = cur->_right; // replace记录待替换的节点,初始化为当前节点的右孩子
                
                // 寻找右子树中的最小节点
                while (replace->_left) {
                    replacePerson = replace; // 更新replacePerson为当前节点
                    replace = replace->_left; // 向左子树继续查找最小值
                }
                
                // 将找到的最小节点的键值复制到当前节点
                cur->_key = replace->_key;
                
                // 删除替换用的节点
                if (replacePerson->_left == replace) { // 如果待删除节点是其父节点的左孩子
                    replacePerson->_left = replace->_right; // 将其父节点的左孩子设置为待删除节点的右孩子
                }
                else { // 如果待删除节点是其父节点的右孩子
                    replacePerson->_right = replace->_right; // 将其父节点的右孩子设置为待删除节点的右孩子
                }
                delete replace; // 删除替换用的节点
            }
            return true; // 成功删除节点
        }
    }
    return false; // 未找到要删除的节点
}

二叉搜索树的实现代码

#include<iostream>
using namespace std;
//key搜索
namespace key {

	//节点结构
	template<class T>
	class BSTNode
	{
	public:
		T _key;
		BSTNode<T>* _left;
		BSTNode<T>* _right;
		//构造
		BSTNode(const T& key) :
			//初始化列表
			_key(key)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

	//树的结构
	template<class T>
	class BSTree
	{
		//typedef BSTNode<T> Node
		using Node = BSTNode<T>;

	public:

		/*插入*/
		bool insert(const T& key) {
			//判断根是否为空
			if (_root == nullptr) {
				_root = new Node(key);
				return true;
			}
			//双指针
			Node* person = nullptr;
			Node* cur = _root;
			//循环
			while (cur)
			{
				if (key >= cur->_key) {
					person = cur;
					cur = cur->_right;
				}
				else if (key < cur->_key)
				{
					person = cur;
					cur = cur->_left;
				}
				else
				{
					return false;
				}
			}
			//链接节点
			cur = new Node(key);
			if (key >= person->_key)
			{
				person->_right = cur;
			}
			else
			{
				person->_left = cur;
			}
			return true;
		}

		/*查找*/
		bool find(const T& key) {
			Node* cur = _root;
			while (cur)
			{
				if (key > cur->_key)
				{
					cur = cur->_right;
				}
				else if (key < cur->_key)
				{
					cur = cur->_left;
				}
				else
				{
					return true;
				}
			}
			return false;
		}

		/*删除*/
		bool erase(const T& key) {
			//双指针
			Node* person = nullptr;
			Node* cur = _root;
			//循环
			while (cur)
			{
				if (key > cur->_key) {
					person = cur;
					cur = cur->_right;
				}
				else if (key < cur->_key)
				{
					person = cur;
					cur = cur->_left;
				}
				else
				{
					//叶子节点+只有一个节点的
					if (cur->_left == nullptr) {
						//判断是否是根节点
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							if (person->_left == cur) {

								person->_left = cur->_right;
							}
							else
							{
								person->_right = cur->_right;
							}
						}
						delete cur;
					}
					else if (cur->_right == nullptr)
					{
						//判断是否是根节点
						if (cur == _root) {
							_root = cur->_left;
						}
						else
						{
							if (person->_left == cur) {

								person->_left = cur->_left;
							}
							else
							{
								person->_right = cur->_left;
							}
						}
						delete cur;
					}
					//两个节点
					else
					{
						Node* replacePerson = cur;//指向cur是防止删除根节点的时候越界
						Node* replace = cur->_right;//右子树的最小值(最合适,可以带左子树试一下,不构成BST)
						while (replace->_left)
						{
							replacePerson = replace;
							replace = replace->_left;
						}
						//将这个值赋值给cur位置
						cur->_key = replace->_key;
						//删除节点
						if (replacePerson->_left == replace)
						{
							replacePerson->_left = replace->_right;
						}
						else
						{
							replacePerson->_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;
};
}

BSTNode 类

这是用于表示树中的节点的类。每个节点包含:

  • _key:键,用于排序。
  • _left 和 _right:指向左子节点和右子节点的指针。

构造函数用于初始化节点的键值和左右子节点指针,默认左右子节点均为 nullptr

BSTree 类

这个类定义了二叉搜索树的行为:

  • 插入操作 (insert):向树中添加一个新的键。如果键已经存在,则插入失败。
  • 查找操作 (find):在树中查找具有指定键的节点。
  • 删除操作 (erase):从树中移除具有指定键的节点。
  • 中序遍历 (InOrder):按升序打印树中的所有键。

内部细节

  • 树的根节点 _root 是一个私有成员变量,初始值为 nullptr 表示空树。
  • 插入、查找和删除操作都使用了“双指针”技术来找到正确的插入或删除位置。
  • 插入操作处理了树为空的情况,直接将新节点作为根节点。
  • 删除操作处理了三种情况:叶子节点、有一个子节点、有两个子节点的情况。

辅助函数

  • _InOrder:递归地执行中序遍历。

二叉搜索树key使用场景

场景1:小区无人值守车库

在这个场景下,小区的物业管理公司将所有购买了车位的业主车牌号录入后台系统中。当车辆试图进入小区时,入口处的系统会自动扫描车牌号,并与后台系统中的车牌号进行比对。如果车牌号存在于二叉搜索树中,则意味着该车辆拥有进入小区的权利,此时系统会自动抬起栏杆让车辆通过;反之,如果车牌号不在二叉搜索树中,则系统会提示该车辆不属于本小区,并阻止其进入。

在这个应用中,车牌号作为唯一的标识符(key),同时也是用来判断车辆是否有权进入的关键信息。由于车牌号通常是唯一的,并且不会经常改变,因此这种情况下非常适合使用二叉搜索树来高效地存储和查询这些车牌号。

场景2:英文文章单词拼写检查

这个场景涉及到检查一篇英文文章中的单词拼写是否正确。为了实现这一功能,首先需要构建一个包含所有正确拼写的单词的词库,并将这些单词按照字母顺序插入到二叉搜索树中。当文章被读取时,系统会对文章中的每个单词进行检查,通过二叉搜索树来确定该单词是否存在。如果一个单词没有出现在二叉搜索树中,这意味着它可能是拼写错误的单词,此时可以将其用波浪线标出或以其他方式高亮显示,以便用户进一步检查和修正。

在这种情况下,单词本身作为key,同时也是要验证的信息。二叉搜索树允许快速定位特定单词,从而使得拼写检查过程更加高效。


二叉搜索树key和key/value使用场景

场景1:简单的中英互译字典

在这个场景中,我们创建一个简单的中英互译字典,其中英文单词作为key,中文翻译作为value。当用户输入一个英文单词时,系统会在二叉搜索树中查找该key,如果找到,就会返回对应的中文翻译。这样就实现了基于key的快速查找功能。需要注意的是,虽然不允许直接修改key,但是value是可以更新的,例如可以在原有条目的基础上增加新的翻译或更正现有的翻译。

场景2:商场无人值守车库

在这个场景下,商场的无人值守车库系统会在车辆入场时记录其车牌号码(作为key)以及入场时间(作为value)。当车辆离开停车场时,出口处的系统会再次扫描车牌号码,并通过二叉搜索树查找对应的入场时间。然后,系统计算停车时长(当前时间减去入场时间),并据此计算停车费用。用户支付费用之后,栏杆升起,车辆可以离开。这种情况下,车牌号码作为唯一标识符,并且保持不变,而入场时间是可以根据实际情况更新的value。

场景3:统计文章中单词出现的次数

在这个场景中,我们需要统计一篇文章中各个单词的出现频率。首先,将文章中的每一个单词作为key,初始化时对应的value设置为出现次数(默认为0)。每当读取到一个新的单词时,系统会在二叉搜索树中查找该单词。如果单词尚未出现过(即value为0或者不存在),则将其加入树中,并将其value设置为1(表示首次出现)。如果单词已经存在,则将对应的value(出现次数)增加1。这样,随着文章的逐字读取,二叉搜索树中就会记录下每个单词的出现次数。

key/value代码实现

namespace key_value {

	// 节点结构定义
	template<class T, class V>
	class BSTNode
	{
	public:
		T _key;       // 存储键
		V _value;     // 存储值
		BSTNode<T, V>* _left;   // 左子节点指针
		BSTNode<T, V>* _right;  // 右子节点指针

		// 构造函数
		BSTNode(const T& key, const V& value) :
			_key(key),
			_value(value),
			_left(nullptr),
			_right(nullptr)
		{}
	};

	// 树的结构定义
	template<class T, class V>
	class BSTree
	{
		// 使用别名简化BSTNode类型的书写
		using Node = BSTNode<T, V>;

	public:
		// 默认构造函数
		BSTree() = default;

		// 拷贝构造函数
		BSTree(const BSTree& tmp) {
			// 深拷贝整棵树
			_root = _Copy(tmp._root);
		}

		// 析构函数
		~BSTree() {
			// 释放所有节点占用的内存
			Destroy(_root);
			_root = nullptr;
		}

		// 拷贝赋值运算符
		BSTree& operator=(BSTree tmp) {
			// 使用交换技巧完成浅拷贝到深拷贝的转换
			swap(_root, tmp._root);
			return *this;
		}

		/* 插入 */
		// 向树中插入键值对
		bool insert(const T& key, const V& value) {
			// 如果树为空,创建新节点作为根节点
			if (_root == nullptr) {
				_root = new Node(key, value);
				return true;
			}
			// 使用双指针找到合适的位置插入新节点
			Node* person = nullptr;
			Node* cur = _root;
			while (cur) {
				if (key >= cur->_key) {
					person = cur;
					cur = cur->_right;
				}
				else if (key < cur->_key) {
					person = cur;
					cur = cur->_left;
				}
				else {
					// 如果键已存在,插入失败
					return false;
				}
			}
			// 创建新节点,并根据比较结果将其插入到合适的位置
			cur = new Node(key, value);
			if (key >= person->_key) {
				person->_right = cur;
			}
			else {
				person->_left = cur;
			}
			return true;
		}

		/* 查找 */
		// 在树中查找指定键对应的节点
		Node* find(const T& key) {
			Node* cur = _root;
			while (cur) {
				if (key > cur->_key) {
					cur = cur->_right;
				}
				else if (key < cur->_key) {
					cur = cur->_left;
				}
				else {
					return cur;
				}
			}
			return nullptr;
		}

		/* 删除 */
		// 从树中删除指定键及其对应的节点
		bool erase(const T& key) {
			// 使用双指针找到要删除的节点
			Node* person = nullptr;
			Node* cur = _root;
			while (cur) {
				if (key > cur->_key) {
					person = cur;
					cur = cur->_right;
				}
				else if (key < cur->_key) {
					person = cur;
					cur = cur->_left;
				}
				else {
					// 处理三种情况:叶节点、只有一个子节点、有两个子节点
					if (cur->_left == nullptr) {
						if (cur == _root) {
							_root = cur->_right;
						}
						else {
							if (person->_left == cur) {
								person->_left = cur->_right;
							}
							else {
								person->_right = cur->_right;
							}
						}
						delete cur;
					}
					else if (cur->_right == nullptr) {
						if (cur == _root) {
							_root = cur->_left;
						}
						else {
							if (person->_left == cur) {
								person->_left = cur->_left;
							}
							else {
								person->_right = cur->_left;
							}
						}
						delete cur;
					}
					else {
						Node* replacePerson = cur;
						Node* replace = cur->_right;
						while (replace->_left) {
							replacePerson = replace;
							replace = replace->_left;
						}
						cur->_key = replace->_key;
						if (replacePerson->_left == replace) {
							replacePerson->_left = replace->_right;
						}
						else {
							replacePerson->_right = replace->_right;
						}
						delete replace;
					}
					return true;
				}
			}
			return false;
		}

		// 类内部调用中序遍历
		void InOrder() {
			_InOrder(_root);
			std::cout << std::endl;
		}

	private:
		// 中序遍历(递归)
		void _InOrder(Node* _root) {
			if (_root == nullptr) {
				return;
			}
			_InOrder(_root->_left);
			std::cout << _root->_key << "-" << _root->_value << " ";
			_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* new_node = new Node(_root->_key, _root->_value);
			new_node->_left = _Copy(_root->_left);
			new_node->_right = _Copy(_root->_right);
			return new_node;
		}

	private:
		Node* _root = nullptr; // 根节点指针
	};
}

下面是对给定代码的一个简明解析:

这段代码定义了一个二叉搜索树(Binary Search Tree, BST),它是一个数据结构,用于存储键值对。该实现使用了 C++ 的模板类来支持不同类型的键和值。

BSTNode 类

这是用于表示树中的节点的类。每个节点包含:

  • _key:键,用于排序。
  • _value:与键关联的值。
  • _left 和 _right:指向左子节点和右子节点的指针。

BSTree 类

这个类定义了二叉搜索树的行为:

  • 构造函数:默认构造函数和拷贝构造函数。
  • 析构函数:清理树中的所有节点。
  • 拷贝赋值运算符:使用交换技巧来实现深拷贝。
  • 插入操作 (insert):向树中添加一个新的键值对。如果键已经存在,则插入失败。
  • 查找操作 (find):在树中查找具有指定键的节点。
  • 删除操作 (erase):从树中移除具有指定键的节点。
  • 中序遍历 (InOrder):按升序打印树中的所有键值对。
  • 辅助函数
    • _InOrder:递归地执行中序遍历。
    • Destroy:释放树中的所有节点。
    • _Copy:递归地复制一棵树。

内部细节

  • 树的根节点 _root 是一个私有成员变量,初始值为 nullptr 表示空树。
  • 插入和删除操作处理了二叉搜索树中常见的几种情况:没有子节点、有一个子节点以及有两个子节点的情况。
  • 拷贝构造函数和拷贝赋值运算符确保了对象的正确复制。

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

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

相关文章

【大模型】初识大模型(非常详细)零基础入门到精通,收藏这一篇就够了_大模型入门

大模型的定义 大模型是指具有数千万甚至数亿参数的深度学习模型。近年来&#xff0c;随着计算机技术和大数据的快速发展&#xff0c;深度学习在各个领域取得了显著的成果&#xff0c;如自然语言处理&#xff0c;图片生成&#xff0c;工业数字化等。为了提高模型的性能&#xf…

MeterSphere的一次越权审计

1 MeterSphere简介 MeterSphere是一个一站式开源持续测试平台&#xff0c;它提供了测试跟踪、接口测试、UI测试和性能测试等功能。它全面兼容JMeter、Selenium等主流开源标准&#xff0c;助力开发和测试团队实现自动化测试&#xff0c;加速软件的高质量交付。MeterSphere 的特点…

Java 微服务框架 HP-SOA v1.1.4

HP-SOA 功能完备&#xff0c;简单易用&#xff0c;高度可扩展的Java微服务框架。 项目主页 : https://www.oschina.net/p/hp-soa下载地址 : https://github.com/ldcsaa/hp-soa开发文档 : https://gitee.com/ldcsaa/hp-soa/blob/master/README.mdQQ Group: 44636872, 66390394…

解决selenium爬虫被浏览器检测问题

文章目录 专栏导读1.问题解析2.代码解析(Edge/Chrome通用)2.1 设置Edge浏览器选项:2.2 尝试启用后台模式2.3 排除启用自动化模式的标志2.4 禁用自动化扩展2.5 设置用户代理2.6 实例化浏览器驱动对象并应用配置2.7 在页面加载时执行JavaScript代码 3.完整代码&#xff08;可直接…

[ IDE ] SEGGER Embedded Studio for RISC-V

一、FILE 二、Edit 三、View 四、Search 五、Navigate 六、Project 七、Build 7.1 编译 先选择一个目标类型&#xff0c;再选择编译。 八、Debug 九、Target 十、Tools 10.1 自定义快捷键 点击菜单项&#xff0c;通过Tools –> Options –> Keyboard&#xff0c;实现自…

初识Linux · 环境变量

目录 前言&#xff1a; 命令行参数 环境变量 直接看现象 更多的环境变量 尝试理解环境变量 前言&#xff1a; 今天介绍的是一个较为陌生的名词&#xff0c;环境变量&#xff0c;在学习环境变量之前&#xff0c;我们需要一定的预备知识&#xff0c;这个预备知识是命令行参…

HarmonyOS学习(十三)——数据管理(二) 关系型数据库

文章目录 1、基本概念2、运行机制3、默认配置与限制4、接口说明5、实战&#xff1a;开发“账本”5.1、创建RdbStore5.2、创建数据库5.3、增加数据5.4、删除数据5.5、修改数据5.6、查询数据5.7、备份数据库5.8、恢复数据库5.9、删除数据库 官方文档地址&#xff1a; 通过关系型…

堆的向下调整算法和TOPK问题

目录 1.什么是堆&#xff1f; 1.1 向下调整建堆的时间复杂度计算 1.2 堆的结构体设计 2.堆的功能实现&#xff1a; 2.1 堆的插入&#xff1a; 2.2 堆的删除&#xff1a; 2.3 堆排序&#xff1a; 2.4 向下调整建堆&#xff1a; 2.5 TOPK问题&#xff1a; 2.6 向上调整算…

对接金蝶云星空调用即时库存信息查询API(附JAVA实现)

文章目录 前言准备工作获取第三方授权权限与授权配置信息集成金蝶云SDK调用实现备注前言 对于有自己商品信息管理后台并且使用金蝶ERP系统管理物料的商家来说,将金蝶上物料的库存信息同步到管理后台就可以不用去金蝶上确认库存了,可以大大简化管理后台的库存变更工作,这篇文…

Call OpenAI API with Python requests is missing a model parameter

题意&#xff1a;使用 Python requests 调用 OpenAI API 时缺少 model 参数。 问题背景&#xff1a; Im trying to call OpenAI API from Python. I know they have their own openai package, but I want to use a generic solution. I chose the requests package for its f…

通义千问重磅开源Qwen2.5,性能超越Llama

Qwen2.5 新闻 9月19日云栖大会&#xff0c;阿里云CTO周靖人发布通义千问新一代开源模型Qwen2.5&#xff0c;旗舰模型Qwen2.5-72B性能超越Llama 405B&#xff0c;再登全球开源大模型王座。Qwen2.5全系列涵盖多个尺寸的大语言模型、多模态模型、数学模型和代码模型&#xff0c;每…

TransUNet: 通过Transformer的视角重新思考U-Net架构在医学图像分割中的设计|文献速递-Transformer架构在医学影像分析中的应用

Title 题目 TransUNet: Rethinking the U-Net architecture design for medical imagesegmentation through the lens of transformers TransUNet: 通过Transformer的视角重新思考U-Net架构在医学图像分割中的设计 01 文献速递介绍 卷积神经网络&#xff08;CNNs&#xff…

计算机毕业设计之:教学平台微信小程序(

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

生信初学者教程(四):软件

文章目录 RRstudioLinux系统其他软件本书是使用R语言编写的教程,用户需要下载R和RStudio软件用于进行分析。 版权归生信学习者所有,禁止商业和盗版使用,侵权必究 R R语言是一种免费的统计计算和图形化编程语言,是一种用于数据分析和统计建模的强大工具。它具有丰富的统计…

CSP-CCF★201912-2回收站选址★

一、问题描述 二、解答 代码&#xff1a; #include<iostream> #include<map> using namespace std; struct rubbish{int x;int y; }rub[1000]; int n; void input(){cin>>n;for(int i0;i<n;i){cin>>rub[i].x>>rub[i].y;} } bool has(int p,…

【machine learning-八-可视化loss funciton】

可视化lossfunction loss funciton可视化损失函数等高图 loss funciton 上一节讲过损失函数&#xff0c;也就是代价函数&#xff0c;它是衡量模型训练好坏的指标&#xff0c;对于线性回归来说&#xff0c;模型、参数、损失函数以及目标如下&#xff1a;、 损失函数的目标当然…

什么品牌超声波清洗机质量好?四大绝佳超声波清洗机品牌推荐!

在快节奏的现代生活中&#xff0c;个人物品的清洁卫生显得至关重要。眼镜、珠宝饰品、手表乃至日常餐厨用具&#xff0c;这些频繁接触的物品极易累积污渍与细菌。拿眼镜为例&#xff0c;缺乏定期清洁会让油渍与尘埃积累&#xff0c;进而成为细菌的温床&#xff0c;靠近眼睛使用…

SCDN是服务器吗?SCDN防御服务器有什么特点?

SCDN确实具有一定的防DDoS攻击能力&#xff0c;SCDN防御服务器有什么特点&#xff1f;高防SCDN通过结合内容分发网络&#xff08;CDN&#xff09;和分布式拒绝服务&#xff08;DDoS&#xff09;防护技术&#xff0c;提供了更全面的网络保护措施。在充满网络攻击的互联网时代&am…

dev c++输出中文乱码解决 printf乱码解决

把编码换成utf8就行 打开eiditor options

左手研发,右手销量,比亚迪舍弃了什么?

早买早享受&#xff0c;晚买享折扣&#xff0c;是近一年来汽车消费市场的真实写照。 A级家轿价格下探至6、7万元&#xff1b;曾经20万起步的主流B级车&#xff0c;如今只要12万元就能入手&#xff1b;即使是BBA等豪华品牌&#xff0c;也开始降价促销换销量。买车更便宜了&…