藤萝垂序——二叉搜索树

一、 二叉搜索树的介绍

二叉搜索树是特殊的二叉树(可以是空树),和二叉树相比,它增加了如下性质:

若对于每一个节点:

1.如果它左子树不为空,那么左子树上的每一个节点的值都小于这个节点的值。

2.如果它右子树不为空,那么右子树上的每一个节点的值都大于这个节点的值。

示意如图:

二、二叉搜索树相关接口的实现

代码整体架构:

由于要通过每一个节点像链表一样访问下一个节点,所以每一个节点包含的信息有:

1.本节点的值

2.左孩子的指针

3.右孩子的指针

将一个节点信息封装成一个结构体

将每一个节点用指针绑定实现节点的链接形成一个树,再定义一个表示树的类,存放根节点的指针,就可以通过根节点的指针访问所有节点,也就意味着这个类记录了整棵树的信息。

树类的成员函数包括:默认成员函数(构造,析构,operator =),节点插入函数,节点删除函数,节点查找函数。

注意:一般数据结构都支持增删查改功能,但是由于二叉树搜索树的性质,操作者肆意改动数据可能导致这个结构不再满足二叉搜索树,所以二叉搜索树不支持定点修改数据。

cpp 复制代码
#include<iostream>
using namespace std;
template<class K>

struct BSTnode
{

public:
	K _key;
	BSTnode* _left;
	BSTnode* _right;

	BSTnode(K key,BSTnode* left=nullptr,BSTnode *right=nullptr):
		_key(key),_left(left),_right(right)
	{ }
	~BSTnode() = default;
};
template <class K>
class BSTree
{
	typedef BSTnode<K> node;
private:
	node*   _root = nullptr;
public:
	BSTree() = default;
	bool insert(K key);

	bool find(K key);
	
	bool erase(K key);
	
	void inOrder();
	
	~BSTree();
	
};

1.二叉搜索树的插入

基于二叉搜索树的性质,我们可以按照如下方式遍历树,找到新加入树的存放位置:

逻辑思路:

1.1首先如果树是空树,直接把这个节点放置在根节点的位置。

1.2根据新加入树的数据大小,从树的根节点开始遍历,小了就再遍历左子树,大了就遍历右子树.

1.3直到遍历到叶子节点的下一个位置,放置该节点。

代码思路:

普通情况:

定义前后指针,二者"步数"相差1,按如上方式遍历,当走在前面的指针走到nullptr(上述中叶子节点的下一个)时,后面的指针刚好走到叶子节点,这样通过判断叶子节点和插入节点的大小,决定新插入节点是叶子节点的左孩子还是右孩子,通过叶子节点的左右指针来链接:

cpp 复制代码
bool insert(K key)
{
	node* newnode = new node(key);
	//空树-改根指针直接接入节点
	if (_root == nullptr)
	{
		_root = newnode;
		return true;
	}
	//非空数,用前后指针链接节点
	else
	{
		node* cur = _root;
		node* curparent = nullptr;
		while (cur != nullptr)
		{
			curparent = cur;
			if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else return false;
		}
		if (curparent->_key < key) curparent->_right = newnode;
		else curparent->_left = newnode;
		return true;
	}
}

问题:

1.一个指针能实现插入吗?

不能,需要前后指针来进行节点的链接操作

2.为什么空树插入和普通插入要在逻辑上分类讨论?

普通逻辑定义前后指针,会使用cur->_key等->访问操作,而空树根节点根本没有这些信息,无法访问。

2.二叉搜索树的查找

逻辑思路:

首先声明:本次实现的二叉搜索树不支持相同数据的多次插入,可以通过插入代码看到当插入节点的数据和正在遍历的节点的数据相同时会直接终止遍历,禁止插入,想要使树可以插入多个相同数据也很简单:修改二叉搜索树的性质:如每个节点左子树存储节点的数据都不大于该节点存储的数据,右子树存储节点的数据都大于该节点存储的数据。或如每个节点右子树存储节点的数据都不小于该节点存储的数据,左子树存储节点的数据都小于该节点存储的数据。

言归正传:当要在二叉搜索树中查找一个值时,只需要从树的根节点开始遍历,当前节点的值比查找值小,遍历左子树,反之遍历右子树,当二值相同时,就找到了,由于不可定点修改,没必要返回这个节点的指针,只需要判断这个值是否已经存入二叉搜索树即可。

代码实现:

cpp 复制代码
bool find(K key)
{
	if (_root == nullptr) return false;
	node* cur = _root;
	while (cur != nullptr)
	{
		if (cur->_key > key)cur = cur->_left;
		else if (cur->_key < key)cur = cur->_right;
		else return true;

	}
	return false;
}

3.二叉搜索树基于数据的删除

逻辑思路:

部分思路和查找相同,要删除值为key的节点,首先要找到这个节点,这还没完,还有找到它的双亲节点,将记录这个节点对应的指针赋值为nullptr

当然,由于每个节点性质不同:

我们按照节点孩子节点的个数划分

为什么这样划分:自己思考一下孩子数小于等于一的节点和孩子数为二的节点的删除方式就知道了。

孩子数为0:

给定这样的一棵二叉搜索树:

3.1.删除1(孩子数为零)

那不是信手拈来,直接删除,将双亲节点中指向它的指针赋值为nullptr.

3.2.删除10(有一个孩子)

也很简单,类似链表删除逻辑:将该节点指向孩子的对应指针赋值给它双亲节点中指向它的指针。

3.3删除3(两个孩子)

有些复杂了,由于8只有两个存储孩子信息的指针,3有两个孩子,直接删除然后赋值必然会造成数据的遗失,改变了树的结构,考虑到堆的删除中也存在类似的问题,我们看看堆中是如何实现的:

堆的删除是将堆顶元素和末尾元素交换,删除末尾元素,然后走一个向下调整算法,使结构还是堆。

但是二叉搜索树中的向下调整算法难以实现,但是我们可以汲取堆删除算法中等效替代,再删除的思想,找到一个方便删除的节点,和期望被删除的节点交换位置,删除交换后易于删除位置的那个节点。

找到这个易于删除且交换后不改变树结构性质的点:

1.期望删除位置左子树最大点(这个点的值比左子树所有值都大,比右子树所有值都小),在树的物理结构中是从期望删除节点位置左子树一直向右走到达的叶子节点。

2.期望删除位置右子树最小点(这个点的值比左子树所有值都大,比右子树所有值都小),在树的物理结构中是从期望删除节点位置右子树一直向左走到达的叶子节点。

接着优化一下算法:将原本计划被交换的"等效节点"的值赋给期望删除的节点,删除等效节点,也可以实现。

接下来是代码实现:

cpp 复制代码
bool erase(K key)
{
	node* cur = _root;
	node* curparent = nullptr;
	while (cur != nullptr)
	{
		//curparent再cur走之后再更新
		if (cur->_key < key)
		{
			curparent = cur;
			cur = cur->_right;
			
		}
		else if (cur->_key > key)
		{
			curparent = cur;
			cur = cur->_left;
		}
		else break;
	}
	if (cur == nullptr) return false;
	//找到待删除的节点和它的双亲节点
	
		if (cur->_left == nullptr)//左孩子为空的节点和双空节点
		{   //判断删除的节点是否是空
			if (curparent == nullptr)
			{
				_root = cur->_right;
				delete cur;
			}
			else
			{
				if (curparent->_right==cur)curparent->_right = cur->_right;
				else curparent->_left = cur->_right;
				delete cur;
			}
			return true;
		}
		else if(cur->_right==nullptr)//右孩子为空的节点
		{   //判断删除的节点是否是空
			if (curparent == nullptr)
			{
				_root = cur->_left;
				delete cur;
			}
			else
			{
				if (curparent->_right==cur)curparent->_right = cur->_left;
				else curparent->_left = cur->_left;
				delete cur;
			}
			return true;
		}   
		else//两个孩子都存在的节点------替代删除法
		{
			//不存在删除根节点一说,替代删除法永远不会删除根
			//找到右子树中最左端的数
			node* rightMin=cur->_right;
			node* minParent= cur;
			while (rightMin->_left!=nullptr)
			{
				minParent = rightMin;
				rightMin=rightMin->_left;
			}
			cur->_key = rightMin->_key;
			if (minParent->_right==rightMin)
				minParent->_right = rightMin->_left;
			else
				minParent->_left = rightMin->_left;
			delete rightMin;
			return true;
		}
}

整体结构框架梳理:

erase(key)

├─ 1. 查找:找到 cur(待删节点)+ curparent(父)

│ └─ 没找到 → return false

└─ 2. 分3种情况删除

├─ 左空 → 右孩子上位

├─ 右空 → 左孩子上位

└─ 左右都有 → 找右子树最小值替换,再删最小值

3.二叉树的遍历

以中序遍历为主:由二叉搜索树的性质,二叉搜索树的中序遍历一定是升序的数据串。

参考深度优先算法写:由于二叉树的遍历要使用递归,不能用无参函数,然而对于整棵二叉搜索树的遍历一般都是从根节点开始遍历,没必要传参,所以我们通过实现私有无参子函数成员,公有实现遍历功能的成员函数通过调用子函数实现遍历:

cpp 复制代码
public:
void inOrder()
{
	_inOrder(_root);
	cout << endl;
}
private:
void _inOrder(node* root)
{
	if (root == nullptr) return;
	_inOrder(root->_left);
	cout << root->_key << " ";
	_inOrder(root->_right);
}

4.二叉搜索树的默认成员函数

4.1构造函数

只需要给根节点赋值为nullptr即可

使用默认生成的构造函数即可

4.2复制构造函数:

我们首先实现一个子函数_copy,实现单个节点的复制和链接操作:

首先生成一个新节点,拷贝key的信息,然后再递归调用此方法拷贝其左右节点的信息,并将左右节点的地址赋值给该节点指向左右孩子的指针。

通过调用子函数_copy实现复制构造,再把子函数返回的地址赋值给_root

4.3析构函数

和二叉搜索树的遍历的逻辑相似,构造一个子函数,析构函数通过调用子函数完成析构并将记录整棵树信息的根节点指针赋值为nullptr。

子函数使用递归实现析构逻辑:

首先需要传一个节点指针,当节点指针为nullptr时,返回。

如果不为空,析构其左子树,然后析构其右子树,最后delete当前节点。

4.4operator =

首先判断是否自我赋值,自我赋值返回对象本身,反之先销毁整棵树调用_destruct,再调用_copy赋值。

默认成员函数代码汇总;

cpp 复制代码
private:
void destruct(node* root)
{
	if (root == nullptr)return;
	destruct(root->_left);
	destruct(root->_right);
	delete root;
	
}

 node* _copy(node * root)
{
	 if (root == nullptr)return nullptr;
	 node* pnew = new node(root->_key);
	 pnew->_left = _copy(root->_left);
	 pnew->_right =_copy(root->_right);
	 return pnew;
}
public:
BSTree() = default;
BSTree(const BSTree &bst)
{
  _root=_copy(bst._root);
}
BSTree& operator =(const BSTree bst)
{
	if (bst == *this)
		return *this;
	else
	{
		destruct(_root);
		_root = _copy(bst._root);
		return *this;
	}
}

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

4.5.插入删除查找函数代码的递归实现:

cpp 复制代码
public:
bool insert(K key)
{
	_insert(_root ,key);
}
bool find(K key)
{
	_find(_root, key);
}
bool erase(K key)
{
	_erase(_root, key);
}
private:
bool _insert(node*&root, K key)
{
	if (root == nullptr)
	{
		root = new node(key);
		return true;
	}
	else if (root->_key < key)
	{
		_insert(root->_right, key);
		return true;
	}
	else if (root->_key > key)
	{
		_insert(root->_left, key);
		return true;
	}
	else return false;

	
}
bool _find(node* root, K key)
{
	if (root == nullptr)return false;
	if (root->_key == key) return true;
	else if (root->_key < key)return _find(root->_right, key);
	else return _find(root->_left, key);
}
bool _erase(node*& root, K key)
{
	if (root == nullptr)return false;
	if (root->_key < key)return _erase(root->_right,key);
	else if (root->_key > key)return _erase(root->_left,key);
	else
	{
		if (root->_left == nullptr)//删除无左孩子和无孩子的节点
		{
			node* temp = root->_right;
			delete root;
			root = temp;
		}
		else if (root->_right == nullptr)//删除无右孩子的节点
		{
			node* temp = root->_left;
			delete root;
			root = temp;
		}
		else
		{
			node* curp = root;
			node* cur = root->_left;
			while (cur->_right)
			{
				cur = cur->_right;
				curp = cur;
			}
			root->_key = cur->_key;
			delete cur;
			curp->_right = nullptr;
		}
		return true;
	}

}

三、二叉搜索树的应用场景

二叉搜索树的应用场景分为两个模型:

一、Key 模型(仅存关键字,只判存在 / 不存在,无附属数据)

核心特点

节点只存 key,没有额外数据;

操作:新增、删除、查询是否存在;

限制:绝对不能修改 Key,改 Key 会直接破坏二叉搜索树有序规则。

生活 & 实际场景

  1. 门禁黑名单 / 访客禁止名单 Key:员工工号 / 手机号 / 人脸编号 用途:系统只需要判断「这个人是否在黑名单里」,不需要额外存储信息。 业务逻辑: 有人违规 → 新增 Key(加入黑名单); 处罚解除 →
    删除 Key(移出黑名单); 人员进门 → 查询 Key 是否存在,存在则禁止通行。 为什么不用改 Key? 工号 /
    手机号本身不会随意变更;如果号码更换,直接删旧 Key、插新 Key,不会原地修改。
  2. 网课 / 软件 已激活设备列表 Key:设备唯一序列号(设备 ID) 用途:校验当前设备是否是已授权设备,只做「存在性判断」。 业务逻辑: 新设备授权 → 插入 Key; 设备解绑 / 注销 → 删除 Key; 打开软件 → 查询设备 ID 是否在列表中。
  3. 抽奖已中奖名单(防重复中奖) Key:用户手机号 / 微信号 用途:保证一人只能中奖一次,仅判断「是否已经中过奖」。 业务逻辑: 用户中奖 → 把手机号插入树中; 再次抽奖时先查询 Key,存在则直接跳过; 活动结束清空列表(批量删除)。
  4. 图书馆禁借图书编号 Key:图书 ISBN / 图书编号 用途:判断某本书是否被列为禁借、破损、下架图书。 只需要知道「这本书能不能借」,无需额外信息。

二、Key/Value 模型(关键字 + 附属数据,可改 Value,不可改 Key)

核心特点 节点同时存 key + value; 增、删、查找依然只以 Key 做比较; 权限: ❌ 不能修改 Key(破坏树结构); ✅

可以随意修改 Value(附属数据变更不影响树规则)。

生活 & 实际场景

  1. 通讯录(最典型) Key:手机号(唯一、有序,作为检索关键字) Value:姓名、备注、分组、头像、住址等个人信息 业务逻辑: 新增联系人:插入 (手机号, 个人信息); 查找联系人:输入手机号(Key),取出对应的姓名 / 备注(Value);
    修改:可以改姓名、备注(改 Value);不能直接改手机号(改 Key) 手机号变了 → 删旧条目,新建一条新 Key+Value;
    删除联系人:根据手机号删除整条节点。
  2. 学生成绩表 Key:学号(唯一有序) Value:各科分数、班级、排名、评语 业务逻辑: 录入学生:插入 (学号, 成绩信息); 查成绩:按学号 (Key) 找到对应分数 (Value); 考试后更新分数:直接修改 Value,不碰学号; 学生退学:按学号删除节点。
  3. 快递驿站包裹管理 Key:取件码 / 快递单号(唯一编号) Value:收件人、电话、存放货架位置、包裹状态 业务逻辑: 快递到站:插入 (取件码, 包裹信息); 用户查件:输入取件码 (Key),查位置 / 状态 (Value); 状态变更(已取出 /
    滞留):修改 Value; 包裹取走:删除对应 Key 节点。
  4. 电商商品库存管理 Key:商品编号(SKU) Value:商品名称、价格、剩余库存、产地 业务逻辑: 上架新品:新增 Key+Value; 查库存:按商品编号 (Key) 读取库存 (Value); 调价、增减库存:只修改 Value;
    商品下架:删除该节点。
  5. 停车场车位管理 Key:车牌号 Value:入场时间、车位号、停车费用 业务逻辑: 车辆进场:插入 (车牌, 停车信息); 计费、查询车位:修改 / 读取 Value; 车辆离场:删除车牌对应的节点。
  6. 银行账户简易管理 Key:银行卡号 Value:户主姓名、余额、开户网点 业务逻辑: 开户:新增节点; 存取款:修改余额(Value); 销户:删除节点; 卡号不变,余额可以反复修改,完全符合 Key/Value 模型。

1.key 模型代码实现汇总:

cpp 复制代码
//BSTree.h
#pragma once
#include<iostream>
using namespace std;
template<class K>

struct BSTnode
{

public:
	K _key;
	BSTnode* _left;
	BSTnode* _right;

	BSTnode(K key,BSTnode* left=nullptr,BSTnode *right=nullptr):
		_key(key),_left(left),_right(right)
	{ }
	~BSTnode() = default;
};
template <class K>
class BSTree
{
	typedef BSTnode<K> node;
private:
	node*   _root = nullptr;
	void _inOrder(node* root)
	{
		if (root == nullptr) return;
		_inOrder(root->_left);
		cout << root->_key << " ";
		_inOrder(root->_right);
	}
	void destruct(node* root)
	{
		if (root == nullptr)return;
		destruct(root->_left);
		destruct(root->_right);
		delete root;
		
	}
	bool _insert(node*&root, K key)
	{
		if (root == nullptr)
		{
			root = new node(key);
			return true;
		}
		else if (root->_key < key)
		{
			_insert(root->_right, key);
			return true;
		}
		else if (root->_key > key)
		{
			_insert(root->_left, key);
			return true;
		}
		else return false;

		
	}
	bool _find(node* root, K key)
	{
		if (root == nullptr)return false;
		if (root->_key == key) return true;
		else if (root->_key < key)return _find(root->_right, key);
		else return _find(root->_left, key);
	}
	bool _erase(node*& root, K key)
	{
		if (root == nullptr)return false;
		if (root->_key < key)return _erase(root->_right,key);
		else if (root->_key > key)return _erase(root->_left,key);
		else
		{
			if (root->_left == nullptr)//删除无左孩子和无孩子的节点
			{
				node* temp = root->_right;
				delete root;
				root = temp;
			}
			else if (root->_right == nullptr)//删除无右孩子的节点
			{
				node* temp = root->_left;
				delete root;
				root = temp;
			}
			else
			{
				node* curp = root;
				node* cur = root->_left;
				while (cur->_right)
				{
					cur = cur->_right;
					curp = cur;
				}
				root->_key = cur->_key;
				delete cur;
				curp->_right = nullptr;
			}
			return true;
		}

	}
	 node* _copy(node * root)
	{
		 if (root == nullptr)return nullptr;
		 node* pnew = new node(root->_key);
		 pnew->_left = _copy(root->_left);
		 pnew->_right =_copy(root->_right);
		 return pnew;
	}
public:
	BSTree() = default;
	BSTree(const BSTree &bst)
	{
      _root=_copy(bst._root);
	}
	BSTree& operator =(const BSTree bst)
	{
		if (bst == *this)
			return *this;
		else
		{
			destruct(_root);
			_root = _copy(bst._root);
			return *this;
		}
	}

	bool insert(K key)
	{
		return _insert(_root ,key);
	}
	//bool insert(K key)
	//{
	//	node* newnode = new node(key);
	//	//空树-改根指针直接接入节点
	//	if (_root == nullptr)
	//	{
	//		_root = newnode;
	//		return true;
	//	}
	//	//非空数,用前后指针链接节点
	//	else
	//	{
	//		node* cur = _root;
	//		node* curparent = nullptr;
	//		while (cur != nullptr)
	//		{
	//			curparent = cur;
	//			if (cur->_key < key)
	//			{
	//				cur = cur->_right;
	//			}
	//			else if (cur->_key > key)
	//			{
	//				cur = cur->_left;
	//			}
	//			else return false;
	//		}
	//		if (curparent->_key < key) curparent->_right = newnode;
	//		else curparent->_left = newnode;
	//		return true;
	//	}
	//}
	bool find(K key)
	{
		return _find(_root, key);
	}
	/*bool find(K key)
	{
		if (_root == nullptr) return false;
		node* cur = _root;
		while (cur != nullptr)
		{
			if (cur->_key > key)cur = cur->_left;
			else if (cur->_key < key)cur = cur->_right;
			else return true;

		}
		return false;
	}*/
	bool erase(K key)
	{
		return _erase(_root, key);
	}
	//bool erase(K key)
	//{
	//	node* cur = _root;
	//	node* curparent = nullptr;
	//	while (cur != nullptr)
	//	{
	//		//curparent再cur走之后再更新
	//		if (cur->_key < key)
	//		{
	//			curparent = cur;
	//			cur = cur->_right;
	//			
	//		}
	//		else if (cur->_key > key)
	//		{
	//			curparent = cur;
	//			cur = cur->_left;
	//		}
	//		else break;
	//	}
	//	if (cur == nullptr) return false;
	//	//找到待删除的节点和它的双亲节点
	//	
	//		if (cur->_left == nullptr)//左孩子为空的节点和双空节点
	//		{   //判断删除的节点是否是空
	//			if (curparent == nullptr)
	//			{
	//				_root = cur->_right;
	//				delete cur;
	//			}
	//			else
	//			{
	//				if (curparent->_right==cur)curparent->_right = cur->_right;
	//				else curparent->_left = cur->_right;
	//				delete cur;
	//			}
	//			return true;
	//		}
	//		else if(cur->_right==nullptr)//右孩子为空的节点
	//		{   //判断删除的节点是否是空
	//			if (curparent == nullptr)
	//			{
	//				_root = cur->_left;
	//				delete cur;
	//			}
	//			else
	//			{
	//				if (curparent->_right==cur)curparent->_right = cur->_left;
	//				else curparent->_left = cur->_left;
	//				delete cur;
	//			}
	//			return true;
	//		}   
	//		else//两个孩子都存在的节点------替代删除法
	//		{
	//			//不存在删除根节点一说,替代删除法永远不会删除根
	//			//找到右子树中最左端的数
	//			node* rightMin=cur->_right;
	//			node* minParent= cur;
	//			while (rightMin->_left!=nullptr)
	//			{
	//				minParent = rightMin;
	//				rightMin=rightMin->_left;
	//			}
	//			cur->_key = rightMin->_key;
	//			if (minParent->_right==rightMin)
	//				minParent->_right = rightMin->_left;
	//			else
	//				minParent->_left = rightMin->_left;
	//			delete rightMin;
	//			return true;
	//		}
	//}
	
	void inOrder()
	{
		_inOrder(_root);
		cout << endl;
	}
	~BSTree()
	{
		destruct(_root);
		_root = nullptr;
	}
};

代码测试:

cpp 复制代码
#include"BSTree.h"
int main()
{
	BSTree<int> tr;
	int arr[] = {1,2,3,7,8,10,6};
	for (int e : arr)
	{
		tr.insert(e);
	}
	tr.inOrder();
	cout << tr.find(1) << endl;
	tr.erase(3);
	BSTree<int> tr1(tr);
	tr.inOrder();
	tr1.inOrder();
	return 0;
}

2.key/val模型实现

cpp 复制代码
#pragma once
#include<iostream>
using namespace std;
template<class K,class V>

struct BSTnode
{

public:
	K _key;
	V _val;
	BSTnode* _left;
	BSTnode* _right;

	BSTnode(K key, V val,BSTnode* left = nullptr, BSTnode* right = nullptr) :
		_key(key),_val(val) ,_left(left), _right(right)
	{
	}
	~BSTnode() = default;
};
template <class K,class V>
class BSTree
{
	typedef BSTnode<K,V> node;
private:
	node* _root = nullptr;
	void _inOrder(node* root)
	{
		if (root == nullptr) return;
		_inOrder(root->_left);
		cout << root->_key<<":"<<root->_val << " ";
		_inOrder(root->_right);
	}
	void destruct(node* root)
	{
		if (root == nullptr)return;
		destruct(root->_left);
		destruct(root->_right);
		delete root;

	}
	bool _insert(node*& root, K key,V val)
	{
		if (root == nullptr)
		{
			root = new node(key,val);
			return true;
		}
		else if (root->_key < key)
		{
			_insert(root->_right, key,val);
			return true;
		}
		else if (root->_key > key)
		{
			_insert(root->_left, key,val);
			return true;
		}
		else return false;


	}
	node* _find(node* root, K key)
	{
		if (root == nullptr)return nullptr;
		if (root->_key == key) return root;
		else if (root->_key < key)return _find(root->_right, key);
		else return _find(root->_left, key);
	}
	bool _erase(node*& root, K key)
	{
		if (root == nullptr)return false;
		if (root->_key < key)return _erase(root->_right, key);
		else if (root->_key > key)return _erase(root->_left, key);
		else
		{
			if (root->_left == nullptr)//删除无左孩子和无孩子的节点
			{
				node* temp = root->_right;
				delete root;
				root = temp;
			}
			else if (root->_right == nullptr)//删除无右孩子的节点
			{
				node* temp = root->_left;
				delete root;
				root = temp;
			}
			else
			{
				node* curp = root;
				node* cur = root->_left;
				while (cur->_right)
				{
					cur = cur->_right;
					curp = cur;
				}
				root->_key = cur->_key;
				delete cur;
				curp->_right = nullptr;
			}
			return true;
		}

	}
	node* _copy(node* root)
	{
		if (root == nullptr)return nullptr;
		node* pnew = new node(root->_key,root->_val);
		pnew->_left = _copy(root->_left);
		pnew->_right = _copy(root->_right);
		return pnew;
	}
public:
	BSTree() = default;
	BSTree(const BSTree& bst)
	{
		_root = _copy(bst._root);
	}
	BSTree& operator =(const BSTree bst)
	{
		if (bst == *this)
			return *this;
		else
		{
			destruct(_root);
			_root = _copy(bst._root);
			return *this;
		}
	}

	bool insert(K key,V val)
	{
		return _insert(_root, key,val);
	}
	//bool insert(K key,V val)
	//{
	//	node* newnode = new node(key,val);
	//	//空树-改根指针直接接入节点
	//	if (_root == nullptr)
	//	{
	//		_root = newnode;
	//		return true;
	//	}
	//	//非空数,用前后指针链接节点
	//	else
	//	{
	//		node* cur = _root;
	//		node* curparent = nullptr;
	//		while (cur != nullptr)
	//		{
	//			curparent = cur;
	//			if (cur->_key < key)
	//			{
	//				cur = cur->_right;
	//			}
	//			else if (cur->_key > key)
	//			{
	//				cur = cur->_left;
	//			}
	//			else return false;
	//		}
	//		if (curparent->_key < key) curparent->_right = newnode;
	//		else curparent->_left = newnode;
	//		return true;
	//	}
	//}
	node* find(K key)
	{
		return _find(_root, key);
	}
	/*node* find(K key)
	{
		if (_root == nullptr) return nullptr;
		node* cur = _root;
		while (cur != nullptr)
		{
			if (cur->_key > key)cur = cur->_left;
			else if (cur->_key < key)cur = cur->_right;
			else return cur;

		}
		return nullptr;
	}*/
	bool erase(K key)
	{
		return_erase(_root, key);
	}
	//bool erase(K key)
	//{
	//	node* cur = _root;
	//	node* curparent = nullptr;
	//	while (cur != nullptr)
	//	{
	//		//curparent再cur走之后再更新
	//		if (cur->_key < key)
	//		{
	//			curparent = cur;
	//			cur = cur->_right;
	//			
	//		}
	//		else if (cur->_key > key)
	//		{
	//			curparent = cur;
	//			cur = cur->_left;
	//		}
	//		else break;
	//	}
	//	if (cur == nullptr) return false;
	//	//找到待删除的节点和它的双亲节点
	//	
	//		if (cur->_left == nullptr)//左孩子为空的节点和双空节点
	//		{   //判断删除的节点是否是空
	//			if (curparent == nullptr)
	//			{
	//				_root = cur->_right;
	//				delete cur;
	//			}
	//			else
	//			{
	//				if (curparent->_right==cur)curparent->_right = cur->_right;
	//				else curparent->_left = cur->_right;
	//				delete cur;
	//			}
	//			return true;
	//		}
	//		else if(cur->_right==nullptr)//右孩子为空的节点
	//		{   //判断删除的节点是否是空
	//			if (curparent == nullptr)
	//			{
	//				_root = cur->_left;
	//				delete cur;
	//			}
	//			else
	//			{
	//				if (curparent->_right==cur)curparent->_right = cur->_left;
	//				else curparent->_left = cur->_left;
	//				delete cur;
	//			}
	//			return true;
	//		}   
	//		else//两个孩子都存在的节点------替代删除法
	//		{
	//			//不存在删除根节点一说,替代删除法永远不会删除根
	//			//找到右子树中最左端的数
	//			node* rightMin=cur->_right;
	//			node* minParent= cur;
	//			while (rightMin->_left!=nullptr)
	//			{
	//				minParent = rightMin;
	//				rightMin=rightMin->_left;
	//			}
	//			cur->_key = rightMin->_key;
	//			if (minParent->_right==rightMin)
	//				minParent->_right = rightMin->_left;
	//			else
	//				minParent->_left = rightMin->_left;
	//			delete rightMin;
	//			return true;
	//		}
	//}

	void inOrder()
	{
		_inOrder(_root);
		cout << endl;
	}
	~BSTree()
	{
		destruct(_root);
		_root = nullptr;
	}
};

测试代码:

cpp 复制代码
//1
int main()
{
	
	BSTree<string, string> dict;
	//BSTree<string, string> copy = dict;
	dict.insert("left", "左边");
	dict.insert("right", "右边");
    dict.insert("insert", "插入");
    dict.insert("string", "字符串");
	
	string str;
	while (cin >> str)
	{
		auto ret = dict.find(str);
		if (ret)
		{
		 cout << "->" << ret->_val << endl;
		 }
			else
			{
		 cout << "无此单词,请重新输入" << endl;
			}
		 }
		
			return 0;
		}
	//2
	int main()
{
	 string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	 BSTree<string, int> countTree;
	 for (const auto& str : arr)
	 {
	 // 先查找水果在不在搜索树中
	 // 1、不在,说明水果第⼀次出现,则插入<水果, 1>
	 // 2、在,则查找到的结点中水果对应的次数++
	 //BSTreeNode<string, int>* ret = countTree.Find(str);
	      auto ret = countTree.find(str);
	      if (ret == NULL)
	        {
	         countTree.insert(str, 1);
	         }
	      else
	         {
	          ret->_val++;
	         }
	 }
	
	 countTree.inOrder();
	
	 return 0;
}

完结撒花!!!

相关推荐
懂点Tech3 小时前
ctfshow MISC入门图片篇(信息附加):misc41解题思路
开发语言·python
catchadmin4 小时前
PHP 8.7 协程新提案 让跨协程代码共享请求身份
开发语言·php
念何架构之路4 小时前
go-grpc设计思想
开发语言·后端·golang
酷炫码神5 小时前
Day 10|Pinia 状态管理:把散落在各组件的数据,收进一个全局仓库
android·开发语言·javascript
careathers5 小时前
【数据结构】从堆到 PriorityQueue:手写一个优先级队列
数据结构
Elastic 中国社区官方博客5 小时前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn1886 小时前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn6 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su6 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
传奇开心果编程7 小时前
【Rust入门练中学】第5课:引用与借用
开发语言·rust·引用·编程语言理论·通用编程语言·借用