C++《二叉搜索树》

在初阶数据结构中我学习了树基础的概念以及了解了顺序结构的二叉树——堆和链式结构二叉树该如何实现,那么接下来我们将进一步的学习二叉树,在此会先后学习到二叉搜索树、AVL树、红黑树;通过这些的学习将让我们更易于理解后面set、map、哈希等的使用以及对底层结构的了解。在此先本篇中我们将了解二次搜索树的概念以及实现二叉搜索树插入、删除等的操作,在了解了这些之后相信在下一篇的set和map的学习你将轻松许多,接下来就开始本篇的学习吧!!!


 1.二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是⼀棵空树,或者是具有以下性质的二叉树:

• 若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值
• 若它的右子树不为空,则右子树上所有结点的值都
大于等于根结点的值
• 它的左右子树也分别为二叉搜索树
• 二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义,后续我们学习map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等值,multimap/multiset支持插入相等值

例如以下左边图示的就是不支持插入相等值得二叉搜索树,右边就是支持插入相等值得二叉搜索树

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

最好得情况下,在此在二叉搜索树当中最为完全二叉树(或者接近完全二叉树),其高度为: O(log2 N)

例如以下示例:

最差情况下,在此⼆叉搜索树退化为单支树(或者类似单支),其高度为:
O(\frac{N}{2})

例如以下示例:

那么通过以上对二叉树最好和最坏情况的分析就可以得出综合而言二叉搜索树增删查改时间复杂度为: O(N)

那么通过以上的分析可以看出二叉搜索树这样的效率显然无法满足我们需求的,我们后续需要继续讲解二叉搜索树的变形,平衡二叉搜索树AVL树和红黑树,才能适用于我们在内存中存储和搜索数据。


在此你可能会想到在二叉搜索树中查找数据不就类似与二分查找吗,那么直接使用之前学习的二分查找不就可以了吗,二分查找的效率相比二叉搜索树还更好,那为什么还要学习了解二叉搜索树呢?
在此二分查找也可以实现 O(logN) 级别的查找效率,但是二分查找有两大缺陷:

1. 需要存储在支持下标随机访问的结构中,并且有序。

在使用到二分查找示我们使用的是数据对应的下标来实现查找,这就使得当被查找的一系列数据不是存储在数组里时就需要先将数据都存储在数组当中并且还要将数据排序成升序,在这个过程中就会有时间和空间上的损耗了 

2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据⼀般需要挪动数据。

在二分查找当中以上提到的缺点其实还不是最致命的,最要命的是当一组数据已经存储在数组当中时并且已经排序好时,如果之后我们要在这组数据当中再插入新的元素或者是要将原数据中的一个元素删除,那么通过之前顺序表的学习我们就知道每插入或者是删除一个元素时间复杂度都为O(N),若多次进行操作这就使得时间复杂度非常高了

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};


通过以上的分析这里也就体现出了平衡二叉搜索树的价值,在插入元素或者是删除元素都相比使用二分查找要有优势。

3. 二叉搜索树的插入

3.1插入分析

在此在二叉搜索树当中插入新的节点就要按照以下步骤进行分析:
1. 树为空,则直接新增结点,赋值给root指针
2. 树不空,按二叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。
3. 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。(要注意的是要保持逻辑⼀致性,插入相等的值不要⼀会往右走,⼀会往左走

例如以下示例:
假设我们要将以下的数组元素依次插入到二叉搜索树当中

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

 所以元素插入完之后二叉搜索树的形式就如下所示:
 

这时如果我们要再插入值为16的元素在二叉搜索树中过程图就如下所示:

 

 再插入值为3的元素在二叉搜索树中过程图就如下所示:
 

 

3.2 插入代码实现 

在实现二叉搜索树的插入代码之前我们先要来实现二叉搜索树大体的结构代码

在此我们先创建一个BSTree.h的头文件在该文件当中来实现二叉搜索树的结构以及各个功能,再创建一个test.cpp的文件用于测试我们实现的二叉搜索树的各个功能是否能满足要求

实现了文件的创建之后接下来就来实现二叉搜索树的大体结构。在此由于二叉搜索树是由各个节点构成的,那么和之前实现链式结构的二叉树一样先要实现表示节点的结构体

 

#include<iostream>
using namespace std;


template<class K>
struct BSTreeNode
{
	K _key;
	BSTreeNode<K>* left;
	BSTreeNode<K>* right;
	BSTreeNode(const K& key)
		:_key(key)
		,left(nullptr)
		,right(nullptr)
	{

	}
};

在此就创建一个结构体BSTreeNode来表示二叉树内节点,在节点当中有三个变量分别是_key表示节点内的数据、_left存储该节点左孩子节点的指针、_right存储该节点右孩子节点的指针。并且将该结构体实现成模板这样就可以支持节点内的元素是任意类型的数据,还有在结构体当中还实现了构造函数。

实现了节点的结构体之后接下来就可以来实现表示二叉搜索树的类,在此我们将类命名为BSTree,实现的是模板类,实现的大体结构如下所示:

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;

public:
    //使用编译器生成的默认构造函数
    BSTree() = default;
    //实现各种功能的成员函数……
	

private:
    //头节点
	Node* _root = nullptr;

};

完成了以上操作接下来就可以来实现插入函数的代码了

注:以下实现的插入函数是数据不支持冗余的情况也就是二叉树当中不支持插入相等的值

bool Insert(const K& key)
{
	//当根节点为空时
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}

	Node* cur = _root;
	//节点的父节点
	Node* parentcur = nullptr;
	while (cur)
	{
		//当key小于当前节点的值
		if (cur->_key < key)
		{
			parentcur = cur;
			cur = cur->right;
		}
		当key大于当前节点的值
		else if (cur->_key > key)
		{
			parentcur = cur;
			cur = cur->left;

		}
		//当key等于当前节点的值
		else
		{
			return false;
		}

	}
	cur = new Node(key);
	//当新节点内的值大于父节点内的值时
	if (parentcur->_key  <  key)
	{
		parentcur->right = cur;
	}
	//当新节点内的值小于父节点内的值时
	else
	{
		parentcur->left = cur;
	}
	return true;

}

4. 二叉搜索树的查找

4.1查找分析

在二叉搜索树中查找节点就要按照以下步骤进行分析:

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

例如以下示例: 

当要查找3时,要找到1的右孩子的那个3值返回 

 

4.2 查找代码实现 

在进行了二叉搜索树的节点查找的分析之后接下来我们就来实现查找代码

注:以下实现的查找函数是数据不支持冗余的情况也就是二叉树当中不支持插入相等的值 

Node* Find(const K& key)
{
	//当根结点为空时
	if (_root == nullptr)
	{
		return nullptr;
	}
	Node* cur = _root;
	Node* parentcur = nullptr;
	while (cur)
	{
		//当key大于当前节点的值
		if (cur->_key < key)
		{
			parentcur = cur;
			cur = cur->right;
		}
		//当key小于当前节点的值
		else if (cur->_key > key)
		{
			parentcur = cur;
			cur = cur->left;
		}
		//当key等于当前节点的值
		else
		{
			return cur;
		}
	}
	//当前二叉树中找不到值为key的节点
	return nullptr;
}

5. 二叉搜索树的删除 

5.1 删除分析

在二叉搜索树当中节点的删除相比插入和查找就复杂一些了,在此会出现以下的多种情况接下来就来一一分析

在此要删除的节点会有以下的四种情况:

1.要删除结点N左右孩子均为空

当要删除节点的节点左右孩子节点都为空时就需要把N结点的父亲对应孩子指针指向空,直接删除N结点

例如以下示例:

在以上二叉搜索树当中要删除值为1的节点就需要将节点1删除之后再将其父节点的左孩子节点指向空

2. 要删除的结点N左孩子位空,右孩子结点不为空

当要删除节点的节点左孩子节点为空时就需要把N结点的父亲对应孩子指针指向N的右孩子,之后直接删除N结点

例如以下示例:


 

在以上二叉搜索树当中要删除值为10的节点就需要将其父节点的左孩子变为原节点的右孩子节点之后再将节点10删除

 3.要删除的结点N右孩子位空,左孩子结点不为空

当要删除节点的节点右孩子节点为空时就需要把N结点的父亲对应孩子指针指向N的左孩子,之后直接删除N结点

例如以下示例:

 

在以上二叉搜索树当中要删除值为14的节点就需要将其父节点的左孩子变为原节点的左孩子节点之后再将节点14删除

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

当要删除的节点左右孩子节点都不为空时,这是就不能像以上的情况一样简单的改变要删除节点的父节点指针,这时由于无法直接删除N结点,这是因为N的两个孩⼦无处安放,只能用替换法删除。在此根据二叉搜索树的性质就需要找N左子树的值最大结点R(最右结点)或者N右子树的值最小结点R(最左结点)替代N,因为这两个结点中任意⼀个,放到N的位置,都满足⼆叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转而变成删除R结点,R结点符合情况2或情况3,可以直接删除。 

例如以下示例:
 

在以上二叉搜索树当中要删除节点值为8的节点和值为3的节点,由于这两个节点都是左右孩子节点都不为空的节点,因此要删除值为8的节点就需要找到其左子树最右的节点或者是右子树最左的节点(在此我们是找右子树最左的节点)

之后交换要删除的节点和找出的节点的值

 

最后删除交换之后值为8的节点即可

 

注:要删除值为3的节点和以上的方式也类型在此就不再细致的讲解

 

5.2 删除代码实现

bool Erase(const K& key)
{
	//当根节点为空时
	if (_root == nullptr)
	{
		return false;
	}
	//当前节点
	Node* cur = _root;
	//当前节点的父节点
	Node* parentcur = _root;
	while (cur)
	{
		//当key的值大于当前节点的值
		if (cur->_key < key)
		{
			parentcur = cur;
			cur = cur->right;
		}
		//当key的值大于当前节点的值
		else if (cur->_key > key)
		{
			parentcur = cur;
			cur = cur->left;
		}
		//当key的值等于当前节点的值
		else
		{
			//当要删除的节点左孩子节点为空
			if (cur->left==nullptr)
			{
				//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放
				if (cur == _root)
				{
					Node* Right = cur->right;
					delete cur;
					_root = Right;
				}
				else
				{
					//当要删除的节点cur是其父节点的左节点时
					if (cur == parentcur->left)
					{
						parentcur->left = cur->right;
						delete cur;
					}
					//当要删除的节点cur是其父节点的右节点时
					else if (cur == parentcur->right)
					{
						parentcur->right = cur->right;
						delete cur;
					}
				}
				
				return true;

			}
			//当要删除的节点右孩子节点为空
			else if (cur->right==nullptr)
			{
				//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放
				if (cur == _root)
				{
					Node* Left = cur->left;
					delete cur;
					_root = Left;
				}
				else
				{
					//当要删除的节点cur是其父节点的左节点时
					if (cur == parentcur->left)
					{
						parentcur->left = cur->left;
						delete cur;
					}
					//当要删除的节点cur是其父节点的右节点时
					else if (cur == parentcur->right)
					{
						parentcur->right = cur->left;
						delete cur;
					}
				}
				return true;
				
			}
			//当要删除的节点左右孩子节点都为空
			else
			{
				Node* minrightParent = cur;
				Node* minright = cur->right;
				//找出当前节点右子树中的最左节点
				while (minright->left)
				{
					minrightParent = minright;
					minright = minright->left;
				}
				cur->_key = minright->_key;
				//当最左节点为其父节点的左节点时
				if (minrightParent->left == minright)
				{
					minrightParent->left = minright->right;
					
				}
				//当最左节点为其父节点的右节点时
				else 
				{
					minrightParent->right = minright->right;
					
				}
				delete minright;
				return true;	

			}

		}
	}

	//当找不到要删除的节点就返回false
	return false;

}

 6. 二叉搜索树遍历

由于二叉搜索树的性质一棵二叉搜索树中序遍历输出的结果就是递增的,那么接下来我们就试着来实现中序遍历的代码

void Inorder()
{
	_Inorder(_root);
	cout << endl;
}



void _Inorder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}
	_Inorder(root->left);
	cout << root->_key <<" ";
	_Inorder(root->right);
}

在此我们实现的中序遍历代码如上所示,那么这时你可能就会有疑问了,为什么要实现两个中序遍历的函数,不是直接使用一个函数就可以满足要求了吗?

在此要考虑到的是在BsTree类以外用户是无法得到二叉搜索树的根节点的,但是在调用中序遍历的函数根据之前我们使用递归的方式是需要一开始就需要将二叉树的根结点作为中序遍历函数的参数的。因此为了解决该问题就再在BSTree类内实现一个函数来调用中序遍历的成员函数,由于是在类的内部在此是可以访问私有的成员变量的,在类外部用户要使用中序遍历时就只需要调用无参的成员函数Inorder就可以得到二叉搜索树中序遍历的结果了。

7. 二叉搜索树完整代码

#include<iostream>
using namespace std;


template<class K>
struct BSTreeNode
{
	K _key;
	BSTreeNode<K>* left;
	BSTreeNode<K>* right;
	BSTreeNode(const K& key)
		:_key(key)
		, left(nullptr)
		, right(nullptr)
	{

	}
};

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;

public:
	//使用编译器生成的默认构造函数
	BSTree() = default;

	bool Insert(const K& key)
	{
		//当根节点为空时
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		Node* cur = _root;
		//节点的父节点
		Node* parentcur = nullptr;
		while (cur)
		{
			//当key小于当前节点的值
			if (cur->_key < key)
			{
				parentcur = cur;
				cur = cur->right;
			}
			当key大于当前节点的值
			else if (cur->_key > key)
			{
				parentcur = cur;
				cur = cur->left;

			}
			//当key等于当前节点的值
			else
			{
				return false;
			}

		}
		cur = new Node(key);
		//当新节点内的值大于父节点内的值时
		if (parentcur->_key < key)
		{
			parentcur->right = cur;
		}
		//当新节点内的值小于父节点内的值时
		else
		{
			parentcur->left = cur;
		}
		return true;

	}

	Node* Find(const K& key)
	{
		//当根结点为空时
		if (_root == nullptr)
		{
			return nullptr;
		}
		Node* cur = _root;
		Node* parentcur = nullptr;
		while (cur)
		{
			//当key大于当前节点的值
			if (cur->_key < key)
			{
				parentcur = cur;
				cur = cur->right;
			}
			//当key小于当前节点的值
			else if (cur->_key > key)
			{
				parentcur = cur;
				cur = cur->left;
			}
			//当key等于当前节点的值
			else
			{
				return cur;
			}
		}
		//当前二叉树中找不到值为key的节点
		return nullptr;
	}


	bool Erase(const K& key)
	{
		//当根节点为空时
		if (_root == nullptr)
		{
			return false;
		}
		//当前节点
		Node* cur = _root;
		//当前节点的父节点
		Node* parentcur = _root;
		while (cur)
		{
			//当key的值大于当前节点的值
			if (cur->_key < key)
			{
				parentcur = cur;
				cur = cur->right;
			}
			//当key的值大于当前节点的值
			else if (cur->_key > key)
			{
				parentcur = cur;
				cur = cur->left;
			}
			//当key的值等于当前节点的值
			else
			{
				//当要删除的节点左孩子节点为空
				if (cur->left == nullptr)
				{
					//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放
					if (cur == _root)
					{
						Node* Right = cur->right;
						delete cur;
						_root = Right;
					}
					else
					{
						//当要删除的节点cur是其父节点的左节点时
						if (cur == parentcur->left)
						{
							parentcur->left = cur->right;
							delete cur;
						}
						//当要删除的节点cur是其父节点的右节点时
						else if (cur == parentcur->right)
						{
							parentcur->right = cur->right;
							delete cur;
						}
					}

					return true;

				}
				//当要删除的节点右孩子节点为空
				else if (cur->right == nullptr)
				{
					//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放
					if (cur == _root)
					{
						Node* Left = cur->left;
						delete cur;
						_root = Left;
					}
					else
					{
						//当要删除的节点cur是其父节点的左节点时
						if (cur == parentcur->left)
						{
							parentcur->left = cur->left;
							delete cur;
						}
						//当要删除的节点cur是其父节点的右节点时
						else if (cur == parentcur->right)
						{
							parentcur->right = cur->left;
							delete cur;
						}
					}
					return true;

				}
				//当要删除的节点左右孩子节点都为空
				else
				{
					Node* minrightParent = cur;
					Node* minright = cur->right;
					//找出当前节点右子树中的最左节点
					while (minright->left)
					{
						minrightParent = minright;
						minright = minright->left;
					}
					cur->_key = minright->_key;
					//当最左节点为其父节点的左节点时
					if (minrightParent->left == minright)
					{
						minrightParent->left = minright->right;

					}
					//当最左节点为其父节点的右节点时
					else
					{
						minrightParent->right = minright->right;

					}
					delete minright;
					return true;

				}

			}
		}

		//当找不到要删除的节点就返回false
		return false;

	}

	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}




private:
	//头节点
	Node* _root = nullptr;

	void _Inorder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Inorder(root->left);
		cout << root->_key << " ";
		_Inorder(root->right);
	}

};

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

8.1 key搜索场景

key搜索场景的形式如下所示:

只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的二叉树搜索树支持增删查,但是不支持修改,修改key破坏搜索树结构了。

在以上我们了解实现的二叉搜索树其实是适用于key的场景,那么接下来就来看看那么场景是属于key的场景

场景1:小区无人值守车库,小区车库买了车位的业主车才能进小区,那么物业会把买了车位的业主的车牌号录入后台系统,⻋辆进入时扫描⻋牌在不在系统中,在则抬杆,不在则提示非本小区⻋辆,无法进入。

 

场景2:检查⼀篇英文文章单词拼写是否正确,将词库中所有单词放入二叉搜索树,读取文章中的单词,查找是否在二叉搜索树中,不在则波浪线标红提示。 

8.2 key/value搜索场景

key/value搜索场景的形式如下所示:

每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字走二叉搜索树的规则进行比较,可以快速查找到key对应的value。key/value的搜索场景实现的二叉树搜索树支持修改,但是不支持修改key,修改key破坏搜索树结构了,可以修改value。

接下来就来将以上我们实现的key二叉搜索树修改为key/value⼆叉搜索树代码,实现代码如下所示:

#include<iostream>
using namespace std;


template<class K,class V>
struct BSTreeNode
{
	K _key;
    V _value;
	BSTreeNode<K ,V>* left;
	BSTreeNode<K ,V>* right;
	BSTreeNode(const K& key, const V& value)
		:_key(key)
        ;_value(value)
		, left(nullptr)
		, right(nullptr)
	{

	}
};

template<class K,class V>
class BSTree
{
	typedef BSTreeNode<K,V> Node;

public:
	//使用编译器生成的默认构造函数
	BSTree() = default;

	bool Insert(const K& key, const V& value)
	{
		//当根节点为空时
		if (_root == nullptr)
		{
			_root = new Node(key,value);
			return true;
		}

		Node* cur = _root;
		//节点的父节点
		Node* parentcur = nullptr;
		while (cur)
		{
			//当key小于当前节点的值
			if (cur->_key < key)
			{
				parentcur = cur;
				cur = cur->right;
			}
			//当key大于当前节点的值
			else if (cur->_key > key)
			{
				parentcur = cur;
				cur = cur->left;

			}
			//当key等于当前节点的值
			else
			{
				return false;
			}

		}
		cur = new Node(key, value);
		//当新节点内的值大于父节点内的值时
		if (parentcur->_key < key)
		{
			parentcur->right = cur;
		}
		//当新节点内的值小于父节点内的值时
		else
		{
			parentcur->left = cur;
		}
		return true;

	}

	Node* Find(const K& key)
	{
		//当根结点为空时
		if (_root == nullptr)
		{
			return nullptr;
		}
		Node* cur = _root;
		Node* parentcur = nullptr;
		while (cur)
		{
			//当key大于当前节点的值
			if (cur->_key < key)
			{
				parentcur = cur;
				cur = cur->right;
			}
			//当key小于当前节点的值
			else if (cur->_key > key)
			{
				parentcur = cur;
				cur = cur->left;
			}
			//当key等于当前节点的值
			else
			{
				return cur;
			}
		}
		//当前二叉树中找不到值为key的节点
		return nullptr;
	}


	bool Erase(const K& key)
	{
		//当根节点为空时
		if (_root == nullptr)
		{
			return false;
		}
		//当前节点
		Node* cur = _root;
		//当前节点的父节点
		Node* parentcur = _root;
		while (cur)
		{
			//当key的值大于当前节点的值
			if (cur->_key < key)
			{
				parentcur = cur;
				cur = cur->right;
			}
			//当key的值大于当前节点的值
			else if (cur->_key > key)
			{
				parentcur = cur;
				cur = cur->left;
			}
			//当key的值等于当前节点的值
			else
			{
				//当要删除的节点左孩子节点为空
				if (cur->left == nullptr)
				{
					//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放
					if (cur == _root)
					{
						Node* Right = cur->right;
						delete cur;
						_root = Right;
					}
					else
					{
						//当要删除的节点cur是其父节点的左节点时
						if (cur == parentcur->left)
						{
							parentcur->left = cur->right;
							delete cur;
						}
						//当要删除的节点cur是其父节点的右节点时
						else if (cur == parentcur->right)
						{
							parentcur->right = cur->right;
							delete cur;
						}
					}

					return true;

				}
				//当要删除的节点右孩子节点为空
				else if (cur->right == nullptr)
				{
					//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放
					if (cur == _root)
					{
						Node* Left = cur->left;
						delete cur;
						_root = Left;
					}
					else
					{
						//当要删除的节点cur是其父节点的左节点时
						if (cur == parentcur->left)
						{
							parentcur->left = cur->left;
							delete cur;
						}
						//当要删除的节点cur是其父节点的右节点时
						else if (cur == parentcur->right)
						{
							parentcur->right = cur->left;
							delete cur;
						}
					}
					return true;

				}
				//当要删除的节点左右孩子节点都为空
				else
				{
					Node* minrightParent = cur;
					Node* minright = cur->right;
					//找出当前节点右子树中的最左节点
					while (minright->left)
					{
						minrightParent = minright;
						minright = minright->left;
					}
					cur->_key = minright->_key;
					//当最左节点为其父节点的左节点时
					if (minrightParent->left == minright)
					{
						minrightParent->left = minright->right;

					}
					//当最左节点为其父节点的右节点时
					else
					{
						minrightParent->right = minright->right;

					}
					delete minright;
					return true;

				}

			}
		}

		//当找不到要删除的节点就返回false
		return false;

	}

	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}




private:
	//头节点
	Node* _root = nullptr;

	void _Inorder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Inorder(root->left);
		cout << root->_key << ":" << root->_val << " ";
		_Inorder(root->right);
	}

};


 

在以上我们了解实现的二叉搜索树其实是适用于key的场景,那么接下来就来看看那么场景是属于key的场景

场景1:商场无人值守车库,入⼝进场时扫描⻋牌,记录车牌和入场时间,出口离场时,扫描牌,查找入场时间,用当前时间-⼊场时间计算出停⻋时长,计算出停车费用,缴费后抬杆,车辆离场。

 

场景1:简单中英互译字典,树的结构中(结点)存储key(文)和vlaue(中文),搜索时输⼊英文,则同时查找到了英文对应的中文。
场景3:统计⼀篇文章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。

以下的两个简单的示例就是使用二叉搜索树的key/val来解决
 

#include"BSTree.h"


int main()
{
	//示例1
//将以下英文单词和对应的中文翻译绑定到一起,当用户输入输入正确的单词就输出其中文意思,否则就输出单词拼写错误
	BSTree<string, string> dict;
	dict.Insert("insert", "插入");
	dict.Insert("erase", "删除");
	dict.Insert("left", "左边");
	dict.Insert("string", "字符串");

	string str;
	while (cin >> str)
	{
		auto ret = dict.Find(str);
		if (ret)
		{
			cout << str << ":" << ret->_val << endl;
		}
		else
		{
			cout << "单词拼写错误" << endl;
		}
	}


    //示例2
//统计字符串数组当中各个水果的出现次数
	string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };
	// 统计水果出现的次
	BSTree<string, int> countTree;
	for (auto str : strs)
	{
		auto ret = countTree.Find(str);
		if (ret == NULL)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			ret->_val++;
		}
	}
	countTree.Inorder();



	return 0;
}

以上就是本篇的区别内容了,希望能得到你的点赞和收藏 ❤️

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ —— 以真我之名 如飞花般绚丽 - 智能指针

目录 1. RAII和智能指针的设计思路 2. C标准库智能指针的使用 2.1 auto_ptr 2.2 unique_ptr 2.3 简单模拟实现auto_ptr和unique_ptr的核心功能 2.4 shared_ptr 2.4.1 make_shared 2.5 weak_ptr 2.6 shared_ptr的缺陷&#xff1a;循环引用问题 3. shared_ptr 和 unique_…

springboot项目使用maven打包,第三方jar问题

springboot项目使用maven package打包为可执行jar后&#xff0c;第三方jar会被打包进去吗&#xff1f; 答案是肯定的。做了实验如下&#xff1a; 第三方jar的项目结构及jar包结构如下&#xff1a;&#xff08;该第三方jar采用的是maven工程&#xff0c;打包为普通jar&#xf…

第六届智能控制、测量与信号处理国际学术会议 (ICMSP 2024)

重要信息 2024年11月29日-12月1日 中国陕西西安石油大学雁塔校区 大会官网&#xff1a;www.icmsp.net 大会简介 第六届智能控制、测量与信号处理国际学术会议&#xff08;ICMSP 2024&#xff09;由西安石油大学、中海油田服务股份有限公司、浙江水利水电学院与中国石油装备…

设计LRU缓存

LRU缓存 LRU缓存的实现思路LRU缓存的操作C11 STL实现LRU缓存自行设计双向链表 哈希表 LRU&#xff08;Least Recently Used&#xff0c;最近最少使用&#xff09;缓存是一种常见的缓存淘汰算法&#xff0c;其基本思想是&#xff1a;当缓存空间已满时&#xff0c;移除最近最少使…

跨平台应用开发框架(1)----Qt(组件篇)

目录 1.Qt 1.Qt 的主要特点 2.Qt的使用场景 3.Qt的版本 2.QtSDK 1.Qt SDK 的组成部分 2.安装 Qt SDK 3.Qt SDK 的优势 3.Qt初识 1.快速上手 widget.cpp mian.cpp widget.h Helloworld.pro 2.对象树 3.坐标系 4.信号和槽 1. 信号和槽的基本概念 2. 信号和槽的…

Vue3+SpringBoot3+Sa-Token+Redis+mysql8通用权限系统

sa-token支持分布式token 前后端代码&#xff0c;地球号: bright12389

专题二十三_动态规划_回文串系列问题_算法专题详细总结

目录 动态规划 回文串系列问题 1. 回⽂⼦串&#xff08;medium&#xff09; 解析&#xff1a; 解决回文串问题&#xff0c;这里提供三个思路&#xff1a; 1.中心扩展法&#xff1a;n^2 / 1 2.马拉车算法&#xff1a;n / n 3.动态规划算法&#xff1a;n^2 / n^2 1.状态表…

ES实用面试题

一、es是什么&#xff0c;为什么要用它&#xff1f; ES通常是Elasticsearch的简称&#xff0c;它是一个基于Lucene构建的开源搜索引擎。Elasticsearch以其分布式、高扩展性和实时数据分析能力而闻名&#xff0c;广泛用于全文搜索、日志分析、实时监控等多种场景。 基本特点&am…

实现在两台宿主机下的docker container 中实现多机器通讯

基于我的实验背景 上位机&#xff1a;ubuntu 20.04 (docker humble 22.04) 下位机&#xff1a;ubuntu 22.04&#xff08;docker noetic 20.04&#xff09; 目标&#xff1a;实现在上位机中的docker container 容器的22.04环境去成功远程访问 非同网段的下位机的20.04的contai…

FakeLocation Linux | Windows关于使用教程一些规范说明

前言:使用教程&#xff08;FakeLocation版本请使用1.2.xxx&#xff09;| (1.3.xxx 未测试) 环境模块&#xff0c;是指代FakeLocation开启以后会把环境弄的异常,环境模块可以保证环境安全Dia 作为软件需要在Lsp框架里面勾选激活使用&#xff0c;并且开启增强模式FakeLocation 请…

指针的奥秘:深入探索内存的秘密

前言 在计算机编程的广阔天地中&#xff0c;指针作为一种独特的数据类型&#xff0c;它不仅是C语言的核心&#xff0c;也是理解计算机内存管理的基石。指针的概念虽然强大&#xff0c;但对于初学者来说&#xff0c;它常常是学习过程中的一个难点。本文旨在揭开指针的神秘面纱&a…

Mairadb 最大连接数、当前连接数 查询

目录 查询数据库 最大连接数 查询当前连接总数 环境 Mariadb 10.11.6 跳转mysql数据库&#xff1a; 查询数据库 最大连接数 show variables like max_connections; 注意; 这个版本不能使用 &#xff1a; show variables like ‘%max_connections%’; 会报错 &#xff…

电影风格城市夜景旅拍Lr调色教程,手机滤镜PS+Lightroom预设下载!

调色教程 电影风格城市夜景旅拍通过 Lightroom 调色&#xff0c;将城市夜晚的景色打造出如同电影画面般的质感和氛围。以独特的色彩和光影处理&#xff0c;展现出城市夜景的魅力与神秘。 预设信息 调色风格&#xff1a;电影风格预设适合类型&#xff1a;人像&#xff0c;街拍…

代码管理之Gitlab

文章目录 Git基础概述场景本地修改未提交&#xff0c;拉取远程代码修改提交本地&#xff0c;远程已有新提交 GitIDEA引入Git拉取仓库代码最后位置 Git基础 概述 workspace 工作区&#xff1a;本地电脑上看到的目录&#xff1b; repository 本地仓库&#xff1a;就是工作区中隐…

【FPGA】Verilog:利用 4 个串行输入- 串行输出的 D 触发器实现 Shift_register

0x00 什么是寄存器 寄存器(Register)是顺序逻辑电路中使用的基本组成部分之一。寄存器用于在数字系统中存储和处理数据。寄存器通常由位(bit)构成,每个位可以存储一个0或1的值。通过寄存器,可以设计出计数器、加法器等各种数据处理电路。 0x01 寄存器的种类 基于 D 触发…

HTML实现 扫雷游戏

前言&#xff1a; 游戏起源与发展 扫雷游戏的雏形可追溯到 1973 年的 “方块&#xff08;cube&#xff09;” 游戏&#xff0c;后经改编出现了 “rlogic” 游戏&#xff0c;玩家需为指挥中心探出安全路线避开地雷。在此基础上&#xff0c;开发者汤姆・安德森编写出了扫雷游戏的…

微信小程序+Vant-自定义选择器组件(单选带筛选

实现效果 筛选是filter&#xff0c;搜索框如有显隐需要&#xff0c;需自行添加配置显隐参数弹出层高度样式需要手动修改&#xff0c;需自行添加配置高度参数.json文件配置"component": true, 实现代码 组件代码 <van-popup show"{{ show }}" posit…

【Linux课程学习】:环境变量:HOME,su与su - 的区别,让程序在哪些用户下能运行的原理,环境变量具有全局性的原因?

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Linux课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 HOME环境变量&#xff1a; PWD环境变量&#…

Java基础 设计模式——针对实习面试

目录 Java基础 设计模式单例模式工厂模式观察者模式策略模式装饰器模式其他设计模式 Java基础 设计模式 单例模式 单例模式&#xff08;Singleton Pattern&#xff09; 定义&#xff1a;确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问这个实例。适用场景&…

QRCode.toDataURL() vue3 uniapp h5在 Android环境下二维码显示不出来

“qrcode”: “^1.5.4” 修改前&#xff08;在浏览器里面是可以加载的&#xff09;&#xff1a; 查资料好像是Android上加载的是canvas&#xff0c;不是加载的img。 修改后&#xff1a; 这里val其实打印出来是svg代码&#xff0c;所以用v-html就好了。