C++ 二叉搜索树(BST)的实现(非递归版本与递归版本)与应用

C++ 二叉搜索树的实现与应用

  • 一.二叉搜索树的特点
  • 二.我们要实现的大致框架
  • 三.Insert
  • 四.InOrder和Find
    • 1.InOrder
    • 2.Find
  • 五.Erase
  • 六.Find,Insert,Erase的递归版本
    • 1.FindR
    • 2.InsertR
    • 3.EraseR
  • 七.析构,拷贝构造,赋值运算符重载
    • 1.析构
    • 2.拷贝构造
    • 3.赋值运算重载
  • 八.Key模型完整代码
  • 九.二叉搜索树的应用
    • 1.Key模型
    • 2.Key-Value模型

二叉搜索树既可以实现为升序版本,也可以实现为降序版本
本文实现为升序版本

一.二叉搜索树的特点

二叉搜索树是一种特殊的二叉树
它的特点是:

1.左子树的所有节点均比根节点的值小
2.右子树的所有节点均比根节点的值大
3.左右子树都是二叉搜索树
4.中序遍历序列是有序的
5.一般二叉搜索树不允许有重复值

当然,二叉搜索树默认是升序的,不过也可以实现成降序的样子
只需要更改一下第1条和第2条即可,
第一条改为左子树的节点都要大于根节点
第二条改为右子树的节点都要小于根节点
此时实现出来的二叉搜索树就是降序的
在这里插入图片描述
例如:这个树就是一个二叉搜索树

二.我们要实现的大致框架

#pragma once
#include <iostream>
using namespace std;
//BST排升序:左孩子小于我,  右孩子大于我
//排降序:   左孩子大于我,  右孩子小于我

//节点的结构体
template <class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left = nullptr;
	BSTreeNode<K>* _right = nullptr;
	K _key;
	BSTreeNode(const K& key)
		:_key(key)
	{}
};

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	//非递归实现insert.find,erase
	bool Insert(const K& key);

	Node* Find(const K& key);

	bool Erase(const K& key);

	//析构函数  后续遍历析构
	~BSTree();
	
	//C++11新语法
	BSTree() = default;//强制生成默认构造
	
	//拷贝构造
	//先序遍历构造
	BSTree(const BSTree<K>& bst);

	//赋值运算符重载:现代版本
	BSTree<K>& operator=(BSTree<K> bst);

	void InOrder()
	{
		_InOrder(_root);
	}

	//递归实现insert.find,erase
	Node* 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);
	}
	
private:
	//拷贝构造函数的子函数
	Node* _Copy(const Node* root);
	//析构函数的子函数
	void _Destroy(Node*& root);
	//中序遍历的子函数
	void _InOrder(Node* root);
	//find的子函数
	Node* _FindR(Node* root, const K& key);
	//insert的子函数
	bool _InsertR(Node*& root, const K& key);
	//erase的子函数
	bool _EraseR(Node*& root, const K& key);
	
	//给根节点_root缺省值nullptr
	Node* _root = nullptr;
};

这是Key模型的版本,最后我们要修改一份Key-Value版本

template <class K>

这里模板给K的原因是:贴合K模型而已,所以没有用T
这里的R : recursion(递归的英文)

//给根节点_root缺省值nullptr
Node* _root = nullptr;

这里直接给根节点_root缺省值nullptr了,编译器默认生成的构造函数就会使用这个缺省值
这里补充一点:

//C++11新语法:给default这个关键字增加了一个含义
BSTree() = default;//强制生成默认构造

三.Insert

学习了二叉搜索树的特点之后,我们来看如何插入一个值
在这里插入图片描述
注意:
1.在遍历查找要插入的位置时一定要记录父节点,否则无法插入
2.最后插入的时候要判断该值与父节点的大小关系,这样才能知道要插入到左侧还是右侧

因此我们就可以写出这样的代码

插入成功,返回true
插入失败(说明插入了重复值),返回false
bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
	Node* cur = _root;
	Node* parent = _root;//记录父亲,因为要能够插入
	while (cur)
	{
		//要插入的值小于父亲,往左找
		if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		//要插入的值大于父亲,往右找
		else if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		//出现了重复元素,BST搜索二叉树不允许出现重复值,因此不允许插入,返回false
		else
		{
			return false;
		}
	}
	//此时cur为空,说明找到了空位置 在此位置插入value
	cur = new Node(key);
	//要插入的元素小于父亲,插入到左侧
	if (parent->_key > key)
	{
		parent->_left = cur;
	}
	//要插入的元素大于父亲,插入到右侧
	else
	{
		parent->_right = cur;
	}
	//插入成功
	return true;
}

四.InOrder和Find

1.InOrder

关于InOrder中序遍历跟普通二叉树的中序遍历是一模一样的
只不过因为要用递归去实现,而且_root是私有变量不能让外界访问到,因此封装了一个子函数,让子函数去递归完成任务,主函数可以被外界调用到,子函数无需提供给外界
同理,后面的Insert,Erase,Find的递归版本都是封装了一个子函数,跟InOrder这方面的思路一样

void InOrder()
{
	_InOrder(_root);
	cout << endl;
}
void _InOrder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}
	_InOrder(root->_left);
	cout << root->_key << " ";
	_InOrder(root->_right);
}

2.Find

学习了Insert之后,Find对我们来说就很简单了
要查找一个值key
1.key小于当前节点的值,往左找
2.key大于当前节点的值,往右找
3.key等于当前节点的值,找到了,返回该节点
4.要查找的当前节点为空节点,返回nullptr,代表查找失败

Node* Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key > key)
		{
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			cur = cur->_right;
		}
		else
		{
			return cur;
		}
	}
	//此时cur为空说明没有找到
	return nullptr;
}

此时我们就可以开始玩这个二叉搜索树了
在这里插入图片描述
可以看出,中序遍历的确是有序的

五.Erase

前面的insert和find都比较简单,接下来的erase就比较复杂了
erase分为4种情况:
对于要删除的节点
1.该节点没有左孩子,也没有右孩子
在这里插入图片描述
不过这里最后删除的时候是不对的,因为14依旧指向13,而13已经被释放了,所以14的_left指针就成为了野指针,怎么办呢?
此时只需要先让该节点的父亲(也就是14)指向空,
然后就可以放心地删除13了
正确的版本:
在这里插入图片描述
2.该节点没有左孩子,但是有右孩子
此时只需要把该节点的右孩子托付给父亲即可
在这里插入图片描述
3.该节点有左孩子,不过没有右孩子
此时只需要把该节点的左孩子托付给父亲即可
其实第一类可以归为第二类或者第三类
在这里插入图片描述
4.该节点既有左孩子,又有右孩子
在这里插入图片描述
其实这里的1就是整棵树当中小于3这个值的最大值
4就是整棵树当中大于3这个值的最小值
他们都可以来代替3这个值

1其实就是要删除的节点的左子树的最大值(最大值就是最右侧节点)
4其实就是要删除的节点的右子树的最小值(最大值就是最左侧节点)

而且1和4都有一个特点:最多只有一个孩子
此时删除1和4就可以借助于第2种或第3种方案了

我们今天按照寻找右子树的最小值的方式来做

注意:之后删除3的操作不能使用递归,因为交换后就不是二叉搜索树了,就无法保证能够找到那个值了

不过上述的讨论当中我们讨论的都是该节点有父亲的情况
都没有讨论下面的这种情况:
5.我要删除的是根节点呢?
(1).根节点没有左孩子也没有右孩子

Node* tmp = _root;
_root=nullptr;
delete tmp;

在这里插入图片描述
(2).根节点只有1个孩子
因为我们知道:一个二叉搜索树的左子树和右子树都是二叉搜索树
比方说根节点只有左孩子,没有右孩子
此时只需要让根节点等于左子树的根节点(也就是根节点的左孩子)即可
在这里插入图片描述
删除根节点之前:
在这里插入图片描述
删除根节点之后:
在这里插入图片描述
可见,这么做的话,删除之后的确也还是二叉搜索树
同理,节点只有右孩子,没有左孩子的时候
只需要让根节点等于右子树的根节点(也就是根节点的右孩子)即可

同理,第一种情况也可以归为第二种情况
(3).根节点有2个孩子
在这里插入图片描述
删除之前:
在这里插入图片描述
删除之后:
在这里插入图片描述
在这里插入图片描述
不过这里也分为两种情况
1.因为查找的是右子树的最左侧节点
也就是一路往左查找,因此最后的时候只需要让我的右孩子成为父亲的左孩子即可

2.不过如果没有一路查找,直接找到了的话
也就是说此时我是父亲的右孩子,那么就要让我的右孩子成为父亲的右孩子了

上面演示的那种情况就属于第二种情况
因此,我们就可以写出这样的代码
里面的注释非常详细,大家如果还不是特别理解的话,
可以对照着边走读代码边画图来更好地理解

//删除成功,返回true
//删除失败,说明没有这个元素,返回false
bool Erase(const K& key)
{
	//1.没有左孩子,没有右孩子 可以归为2,3中的任意一类
	//2.有右孩子,没有左孩子
	//3.有左孩子,没有右孩子
	//4.有左孩子,也有右孩子
	Node* cur = _root;
	Node* parent = cur;//父亲
	while (cur)
	{
		//往左找
		if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		//往右找
		else if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		//找到了
		else
		{
			//1.有右孩子,没有左孩子
			//此时只需要让他的右孩子代替它的位置即可(也就是把自己的右孩子给父亲,然后删除自己即可)
			if (cur->_left == nullptr)
			{
				//要删除的是_root,且_root没有左孩子
				//那么让右孩子变成root即可
				if (cur == _root)
				{
					_root = cur->_right;
					delete cur;
				}
				//说明我是父亲的左孩子
				if (cur == parent->_left)
				{
					//就让我的右孩子成为父亲的左孩子
					parent->_left = cur->_right;
					delete cur;
				}
				//说明我是父亲的右孩子
				else
				{
					//就让我的右孩子成为父亲的右孩子
					parent->_right = cur->_right;
					delete cur;
				}
			}
			//2.有左孩子,没有右孩子
			else if (cur->_right == nullptr)
			{
				//要删除的是_root,且_root没有左孩子
				//那么让右孩子变成root即可
				if (cur == _root)
				{
					_root = cur->_left;
					delete cur;
				}
				//说明我是父亲的左孩子
				if (cur == parent->_left)
				{
					//就让我的左孩子成为父亲的左孩子
					parent->_left = cur->_left;
					delete cur;
				}
				//说明我是父亲的右孩子
				else
				{
					//就让我的左孩子成为父亲的右孩子
					parent->_right = cur->_left;
					delete cur;
				}
			}
			//3.有左孩子,也有右孩子
			//我既可以让左子树的最大值替代我,也可以让右子树的最小值替代我
			//这里就找右子树的最小值吧,右子树的最小值就是右子树的最左侧节点
			//找到右子树中的最小值,将他的值跟我交换,然后删除刚才那个节点
			//注意:"删除刚才那个节点"的操作不能使用递归,因为交换后就不是BST了,就无法保证能够找到那个值了
			else
			{
				parent = cur;
				Node* MinOfRight = cur->_right;
				while (MinOfRight->_left)
				{
					parent = MinOfRight;
					MinOfRight = MinOfRight->_left;
				}
				//开始交换
				swap(cur->_key, MinOfRight->_key);
				//然后删除MinOfRight
				//1.的确向下查找了
				//此时MinOfRight就是parent的左孩子
				//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的左孩子,然后就可以删除了
				if (parent->_left == MinOfRight)
				{
					parent->_left = MinOfRight->_right;
					delete MinOfRight;
				}
				//2.没有继续往下查找
				//此时MinOfRight就是parent的右孩子
				//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的右孩子,然后就可以删除了
				else
				{
					parent->_right = MinOfRight->_right;
					delete MinOfRight;
				}
			}
			//删除成功
			return true;
		}
	}
	//此时cur为空说明没有找到
	return false;
}

六.Find,Insert,Erase的递归版本

1.FindR

Find的递归版本就很简单了:
假设要查找的值是Key
如果当前节点的值==key:查到了,返回当前节点即可
如果当前节点的值>key:说明当前节点值太大,往左找
如果当前节点的值<key:说明当前节点值太小,往右找

Node* FindR(const K& key)
{
	return _FindR(_root,key);
}
Node* _FindR(Node* root, const K& key)
{
	if (root == nullptr)
	{
		return nullptr;
	}
	if (root->_key > key)
	{
		return _FindR(root->_left, key);
	}
	else if(root->_key < key)
	{
		return _FindR(root->_right, key);
	}
	else
	{
		return root;
	}
}

2.InsertR

如果当前节点是空节点:说明找到了空位置,插入即可
如果当前节点的值>key:说明当前节点值太大,往左找插入位置
如果当前节点的值<key:说明当前节点值太小,往右找插入位置
如果当前节点的值==key:说明重复了,返回false,不能插入重复元素

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;
	}
	else if (root->_key > key)
	{
		return _InsertR(root->_left, key);
	}
	else if(root->_key < key)
	{
		return _InsertR(root->_right, key);
	}
	else
	{
		return false;
	}
}

这里特别巧妙的一点在于:只要加上引用
那么就可以不用传递父节点了
因为root就是上一个节点的左孩子或者右孩子的别名,改变root会影响到上一个节点的左孩子或者右孩子

这里引用作为参数的价值就显得格外巧妙了

3.EraseR

这里是递归版本的erase,
而且要删除的节点跟MinOfRight交换之后,右子树是一个二叉搜索树
因此后面删除MinOfRight的时候可以复用,直接在右子树上面删除MinOfRight即可
而且对于删除根节点也是如此

这里依旧使用引用作为参数,它的巧妙之处在于修改指向时特别方便了,无需传入父亲节点

bool EraseR(const K& key)
{
	return _EraseR(_root, key);
}
bool _EraseR(Node*& root, const K& key)
{
	if (root == nullptr)
	{
		return false;
	}
	//1.往左找,在左子树里面删除key
	if (root->_key > key)
	{
		return _EraseR(root->_left, key);
	}
	//2.往右找,在右子树里面删除key
	else if (root->_key < key)
	{
		return _EraseR(root->_right, key);
	}
	// 当前的根节点
	else
	{
		//root不仅仅是root,root是父亲的孩子的别名
		//因此只需要改变root就可以改变父亲的孩子了
		if (root->_left == nullptr)
		{
			//不要忘了保存root
			Node* del = root;
			root = root->_right;//这里不是迭代,而是修改指向,是把我的右孩子托付给父亲
			delete del;
			return true;
		}
		else if (root->_right == nullptr)
		{
			Node* del = root;
			root = root->_left;
			delete del;
			return true;
		}
		else
		{
			Node* MinOfRight = root->_right;
			while (MinOfRight->_left)
			{
				MinOfRight = MinOfRight->_left;
			}
			swap(root->_key, MinOfRight->_key);
			//注意:现在是递归版本,参数可以传入节点,此时这棵树不是BST,但是root的右子树是BST
			//所以此时递归删除root->_right上的key值即可
			//而且也适用于直接删除根节点的情况
			_EraseR(root->_right, key);
		}
	}
	return true;
}

七.析构,拷贝构造,赋值运算符重载

1.析构

跟二叉树的销毁一样,后序遍历销毁
依旧是采用递归版本

//析构函数  后续遍历析构
~BSTree()
{
	_Destroy(_root);
}
void _Destroy(Node*& root)
{
	if (root == nullptr) return;
	_Destroy(root->_left);
	_Destroy(root->_right);
	delete root;
	root = nullptr;
}

2.拷贝构造

先序遍历构造
先构造根节点,然后递归构造左子树和右子树
最后返回根节点

//拷贝构造
//先序遍历构造
BSTree(const BSTree<K>& bst)
{
	_root = _Copy(bst._root);
}
Node* _Copy(const Node* root)
{
	if (root == nullptr)
	{
		return nullptr;
	}
	Node* NewRoot = new Node(root->_key);
	NewRoot->_left = _Copy(root->_left);
	NewRoot->_right = _Copy(root->_right);
	return NewRoot;
}

3.赋值运算重载

实现了拷贝构造之后就可以
直接现代写法了

//赋值运算符重载
BSTree<K>& operator=(BSTree<K> bst)
{
	std::swap(_root, bst._root);
	return *this;
}

八.Key模型完整代码

template <class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left = nullptr;
	BSTreeNode<K>* _right = nullptr;
	K _key;
	BSTreeNode(const K& key)
		:_key(key)
	{}
};

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	//非递归实现insert.find,erase
	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		Node* cur = _root;
		Node* parent = _root;//记录父亲,因为要能够插入
		while (cur)
		{
			//要插入的值小于父亲,插入到左子树当中
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			//要插入的的值大于父亲,插入到右子树当中
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			//出现了重复元素,BST搜索二叉树不允许出现重复值,因此不允许插入,返回false
			else
			{
				return false;
			}
		}
		//此时cur为空,在此位置插入value
		cur = new Node(key);
		//要插入的元素小于父亲,插入到左子树当中
		if (parent->_key > key)
		{
			parent->_left = cur;
		}
		//要插入的元素大于父亲,插入到右子树当中
		else
		{
			parent->_right = cur;
		}
		//插入成功
		return true;
	}

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else
			{
				return cur;
			}
		}
		//此时cur为空说明没有找到
		return nullptr;
	}

	bool Erase(const K& key)
	{
		//1.没有左孩子,没有右孩子 可以归为2,3中的任意一类
		//2.有右孩子,没有左孩子
		//3.有左孩子,没有右孩子
		//4.有左孩子,也有右孩子
		Node* cur = _root;
		Node* parent = cur;//父亲
		while (cur)
		{
			//往左找
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			//往右找
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			//找到了
			else
			{
				//1.有右孩子,没有左孩子
				//此时只需要让他的右孩子代替它的位置即可(也就是把自己的右孩子给父亲,然后删除自己即可)
				if (cur->_left == nullptr)
				{
					//要删除的是_root,且_root没有左孩子
					//那么让右孩子变成root即可
					if (cur == _root)
					{
						_root = cur->_right;
						delete cur;
					}
					//说明我是父亲的左孩子
					if (cur == parent->_left)
					{
						//就让我的右孩子成为父亲的左孩子
						parent->_left = cur->_right;
						delete cur;
					}
					//说明我是父亲的右孩子
					else
					{
						//就让我的右孩子成为父亲的右孩子
						parent->_right = cur->_right;
						delete cur;
					}
				}
				//2.有左孩子,没有右孩子
				else if (cur->_right == nullptr)
				{
					//要删除的是_root,且_root没有左孩子
					//那么让右孩子变成root即可
					if (cur == _root)
					{
						_root = cur->_left;
						delete cur;
					}
					//说明我是父亲的左孩子
					if (cur == parent->_left)
					{
						//就让我的左孩子成为父亲的左孩子
						parent->_left = cur->_left;
						delete cur;
					}
					//说明我是父亲的右孩子
					else
					{
						//就让我的左孩子成为父亲的右孩子
						parent->_right = cur->_left;
						delete cur;
					}
				}
				//3.有左孩子,也有右孩子
				//我既可以让左子树的最大值替代我,也可以让右子树的最小值替代我
				//这里就找右子树的最小值吧,右子树的最小值就是右子树的最左侧节点
				//找到右子树中的最小值,将他的值跟我交换,然后删除刚才那个节点
				//注意:"删除刚才那个节点"的操作不能使用递归,因为交换后就不是BST了,就无法保证能够找到那个值了
				else
				{
					parent = cur;
					Node* MinOfRight = cur->_right;
					while (MinOfRight->_left)
					{
						parent = MinOfRight;
						MinOfRight = MinOfRight->_left;
					}
					//开始交换
					swap(cur->_key, MinOfRight->_key);
					//然后删除MinOfRight
					//1.的确向下查找了
					//此时MinOfRight就是parent的左孩子
					//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的左孩子,然后就可以删除了
					if (parent->_left == MinOfRight)
					{
						parent->_left = MinOfRight->_right;
						delete MinOfRight;
					}
					//2.没有继续往下查找
					//此时MinOfRight就是parent的右孩子
					//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的右孩子,然后就可以删除了
					else
					{
						parent->_right = MinOfRight->_right;
						delete MinOfRight;
					}
				}
				//删除成功
				return true;
			}
		}
		//此时cur为空说明没有找到
		return false;
	}

	//析构函数  后续遍历析构
	~BSTree()
	{
		_Destroy(_root);
	}
	//C++11新语法
	BSTree() = default;//强制生成默认构造
	//拷贝构造
	//先序遍历构造
	BSTree(const BSTree<K>& bst)
	{
		_root = _Copy(bst._root);
	}

	//赋值运算符重载
	BSTree<K>& operator=(BSTree<K> bst)
	{
		std::swap(_root, bst._root);
		return *this;
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	//递归实现insert.find,erase
	Node* 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);
	}
private:
	Node* _Copy(const Node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		Node* NewRoot = new Node(root->_key);
		NewRoot->_left = _Copy(root->_left);
		NewRoot->_right = _Copy(root->_right);
		return NewRoot;
	}

	void _Destroy(Node*& root)
	{
		if (root == nullptr) return;
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
		root = nullptr;
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}

	Node* _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		if (root->_key > key)
		{
			return _FindR(root->_left, key);
		}
		else if(root->_key < key)
		{
			return _FindR(root->_right, key);
		}
		else
		{
			return root;
		}
	}

	bool _InsertR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		else if (root->_key > key)
		{
			return _InsertR(root->_left, key);
		}
		else if(root->_key < key)
		{
			return _InsertR(root->_right, key);
		}
		else
		{
			return false;
		}
	}

	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}
		//1.往左找
		if (root->_key > key)
		{
			return _EraseR(root->_left, key);
		}
		//2.往右找
		else if (root->_key < key)
		{
			return _EraseR(root->_right, key);
		}
		// 删除
		else
		{
			//root不仅仅是root,root是父亲的孩子的别名,让root成为root的右孩子即可
			if (root->_left == nullptr)
			{
				Node* del = root;
				root = root->_right;//这里不是迭代,而是修改指向,托孤
				delete del;
				return true;
			}
			else if (root->_right == nullptr)
			{
				Node* del = root;
				root = root->_left;
				delete del;
				return true;
			}
			else
			{
				Node* MinOfRight = root->_right;
				while (MinOfRight->_left)
				{
					MinOfRight = MinOfRight->_left;
				}
				swap(root->_key, MinOfRight->_key);
				//注意:现在是递归版本,参数可以传入节点,此时这棵树不是BST,但是root的右子树是BST
				//所以此时递归删除root->_right上的key值即可
				//而且对于直接删除_root也没有任何影响
				_EraseR(root->_right, key);
			}
		}
		return true;
	}
	Node* _root = nullptr;
};

九.二叉搜索树的应用

1.Key模型

在这里插入图片描述

2.Key-Value模型

在这里插入图片描述
下面我们把刚才Key模型的代码改为Key-Value模型
只需要改一下:
1.BSTreeNode节点
2.insert
3.InOrder的打印即可
其他地方都不需要修改

namespace kv
{
	template <class K,class V>
	struct 
	{
		BSTreeNode<K,V>* _left = nullptr;
		BSTreeNode<K,V>* _right = nullptr;
		K _key;
		V _value;
		BSTreeNode(const K& key,const V& value)
			:_key(key)
			,_value(value)
		{}
	};

	template<class K,class V>
	class BSTree
	{
		typedef BSTreeNode<K,V> Node;
	public:
		//非递归实现insert.find,erase
		bool Insert(const K& key,const V& value)
		{
			if (_root == nullptr)
			{
				_root = new Node(key,value);
				return true;
			}
			Node* cur = _root;
			Node* parent = _root;//记录父亲,因为要能够插入
			while (cur)
			{
				//要插入的值小于父亲,插入到左子树当中
				if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				//要插入的的值大于父亲,插入到右子树当中
				else if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				//出现了重复元素,BST搜索二叉树不允许出现重复值,因此不允许插入,返回false
				else
				{
					return false;
				}
			}
			//此时cur为空,在此位置插入value
			cur = new Node(key,value);
			//要插入的元素小于父亲,插入到左子树当中
			if (parent->_key > key)
			{
				parent->_left = cur;
			}
			//要插入的元素大于父亲,插入到右子树当中
			else
			{
				parent->_right = cur;
			}
			//插入成功
			return true;
		}

		Node* Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else if (cur->_key < key)
				{
					cur = cur->_right;
				}
				else
				{
					return cur;
				}
			}
			//此时cur为空说明没有找到
			return nullptr;
		}

		bool Erase(const K& key)
		{
			//1.没有左孩子,没有右孩子 可以归为2,3中的任意一类
			//2.有右孩子,没有左孩子
			//3.有左孩子,没有右孩子
			//4.有左孩子,也有右孩子
			Node* cur = _root;
			Node* parent = cur;//父亲
			while (cur)
			{
				//往左找
				if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				//往右找
				else if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				//找到了
				else
				{
					//1.有右孩子,没有左孩子
					//此时只需要让他的右孩子代替它的位置即可(也就是把自己的右孩子给父亲,然后删除自己即可)
					if (cur->_left == nullptr)
					{
						//要删除的是_root,且_root没有左孩子
						//那么让右孩子变成root即可
						if (cur == _root)
						{
							_root = cur->_right;
							delete cur;
						}
						//说明我是父亲的左孩子
						if (cur == parent->_left)
						{
							//就让我的右孩子成为父亲的左孩子
							parent->_left = cur->_right;
							delete cur;
						}
						//说明我是父亲的右孩子
						else
						{
							//就让我的右孩子成为父亲的右孩子
							parent->_right = cur->_right;
							delete cur;
						}
					}
					//2.有左孩子,没有右孩子
					else if (cur->_right == nullptr)
					{
						//要删除的是_root,且_root没有左孩子
						//那么让右孩子变成root即可
						if (cur == _root)
						{
							_root = cur->_left;
							delete cur;
						}
						//说明我是父亲的左孩子
						if (cur == parent->_left)
						{
							//就让我的左孩子成为父亲的左孩子
							parent->_left = cur->_left;
							delete cur;
						}
						//说明我是父亲的右孩子
						else
						{
							//就让我的左孩子成为父亲的右孩子
							parent->_right = cur->_left;
							delete cur;
						}
					}
					//3.有左孩子,也有右孩子
					//我既可以让左子树的最大值替代我,也可以让右子树的最小值替代我
					//这里就找右子树的最小值吧,右子树的最小值就是右子树的最左侧节点
					//找到右子树中的最小值,将他的值跟我交换,然后删除刚才那个节点
					//注意:"删除刚才那个节点"的操作不能使用递归,因为交换后就不是BST了,就无法保证能够找到那个值了
					else
					{
						parent = cur;
						Node* MinOfRight = cur->_right;
						while (MinOfRight->_left)
						{
							parent = MinOfRight;
							MinOfRight = MinOfRight->_left;
						}
						//开始交换
						swap(cur->_key, MinOfRight->_key);
						//然后删除MinOfRight
						//1.的确向下查找了
						//此时MinOfRight就是parent的左孩子
						//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的左孩子,然后就可以删除了
						if (parent->_left == MinOfRight)
						{
							parent->_left = MinOfRight->_right;
							delete MinOfRight;
						}
						//2.没有继续往下查找
						//此时MinOfRight就是parent的右孩子
						//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的右孩子,然后就可以删除了
						else
						{
							parent->_right = MinOfRight->_right;
							delete MinOfRight;
						}
					}
					//删除成功
					return true;
				}
			}
			//此时cur为空说明没有找到
			return false;
		}

		//析构函数  后续遍历析构
		~BSTree()
		{
			_Destroy(_root);
		}
		//C++11新语法
		BSTree() = default;//强制生成默认构造
		//拷贝构造
		//先序遍历构造
		BSTree(const BSTree<K,V>& bst)
		{
			_root = _Copy(bst._root);
		}

		//赋值运算符重载
		BSTree<K,V>& operator=(BSTree<K,V> bst)
		{
			std::swap(_root, bst._root);
			return *this;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

		//递归实现insert.find,erase
		Node* FindR(const K& key)
		{
			return _FindR(_root, key);
		}

		bool InsertR(const K& key,const V& value)
		{
			return _InsertR(_root, key,value);
		}

		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}
	private:
		Node* _Copy(const Node* root)
		{
			if (root == nullptr)
			{
				return nullptr;
			}
			Node* NewRoot = new Node(root->_key);
			NewRoot->_left = _Copy(root->_left);
			NewRoot->_right = _Copy(root->_right);
			return NewRoot;
		}

		void _Destroy(Node*& root)
		{
			if (root == nullptr) return;
			_Destroy(root->_left);
			_Destroy(root->_right);
			delete root;
			root = nullptr;
		}

		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			_InOrder(root->_left);
			cout << root->_key << ":" << root->_value << " ";
			_InOrder(root->_right);
		}

		Node* _FindR(Node* root, const K& key)
		{
			if (root == nullptr)
			{
				return nullptr;
			}
			if (root->_key > key)
			{
				return _FindR(root->_left, key);
			}
			else if (root->_key < key)
			{
				return _FindR(root->_right, key);
			}
			else
			{
				return root;
			}
		}

		bool _InsertR(Node*& root, const K& key,const V& value)
		{
			if (root == nullptr)
			{
				root = new Node(key,value);
				return true;
			}
			else if (root->_key > key)
			{
				return _InsertR(root->_left, key);
			}
			else if (root->_key < key)
			{
				return _InsertR(root->_right, key);
			}
			else
			{
				return false;
			}
		}

		bool _EraseR(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				return false;
			}
			//1.往左找
			if (root->_key > key)
			{
				return _EraseR(root->_left, key);
			}
			//2.往右找
			else if (root->_key < key)
			{
				return _EraseR(root->_right, key);
			}
			// 删除
			else
			{
				//root不仅仅是root,root是父亲的孩子的别名,让root成为root的右孩子即可
				if (root->_left == nullptr)
				{
					Node* del = root;
					root = root->_right;//这里不是迭代,而是修改指向,托孤
					delete del;
					return true;
				}
				else if (root->_right == nullptr)
				{
					Node* del = root;
					root = root->_left;
					delete del;
					return true;
				}
				else
				{
					Node* MinOfRight = root->_right;
					while (MinOfRight->_left)
					{
						MinOfRight = MinOfRight->_left;
					}
					swap(root->_key, MinOfRight->_key);
					//注意:现在是递归版本,参数可以传入节点,此时这棵树不是BST,但是root的右子树是BST
					//所以此时递归删除root->_right上的key值即可
					//而且对于直接删除_root也没有任何影响
					_EraseR(root->_right, key);
				}
			}
			return true;
		}
		Node* _root = nullptr;
	};
}

下面我们来测试一下
一个是统计单词出现的次数
一个是英汉互译的词典

void TestBSTree()
{
	string strs[] = { "apple","Banana","Grape","Mango","apple","Banana" ,"apple","Mango" ,"Mango" ,"Mango" ,"Mango" };
	// 统计单词出现的次数
	kv::BSTree<string, int> countTree;
	for (auto str : strs)
	{
		auto ret = countTree.Find(str);
		if (ret == NULL)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	countTree.InOrder();

	//英汉互译的词典
	kv::BSTree<string, string> dict;
	dict.Insert("insert", "插入");
	dict.Insert("erase", "删除");
	dict.Insert("BST", "二叉搜索树");
	dict.Insert("KV", "key-value模型");

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

在这里插入图片描述

以上就是C++ 二叉搜索树(BST)的实现(非递归版本与递归版本)与应用的全部内容,希望能对大家有所帮助!

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

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

相关文章

Prometheus 监控笔记(1):你真的会玩监控吗?

认识Prometheus Prometheus 是一种开源的系统和服务监控工具&#xff0c;最初由 SoundCloud 开发&#xff0c;后来成为继 Kubernetes 之后云原生生态系统中的一部分。在 Kubernetes 容器管理系统中&#xff0c;通常会搭配 Prometheus 进行监控&#xff0c;同时也支持多种 Expo…

股票价格预测 | Python实现基于ARIMA和LSTM的股票预测模型(含XGBoost特征重要性衡量)

文章目录 效果一览文章概述模型描述源码设计效果一览 文章概述 Python实现基于ARIMA和LSTM的股票预测模型(Stock-Prediction) Data ExtractionFormatting data for time seriesFeature engineering(Feature Importance using X

2023NEFU实习项目解析 - 中俄贸易供需服务平台

文章目录 项目概述项目初始化搭建项目初始框架配置Tomcat建立项目数据库编写统一返回类及其工具类编写数据库工具类通过Filter解决Response返回中文乱码问题使用Filter解决权限校验问题 项目主干开发用户登录企业管理&#xff08;分页查询原生实现&#xff09;上传VIP申请书模板…

【ArkTS】生命周期

页面生命周期 通常Entry修饰的组件称为页面&#xff0c;其拥有页面生命周期 onPageShow&#xff1a;页面每次显示时触发。onPageHide&#xff1a;页面每次隐藏时触发&#xff08;通常是路由跳转到其他页面了&#xff09;。onBackPress&#xff1a;当用户点击返回按钮时时触发…

【LeetCode刷题-哈希表】--187.重复的DNA序列

187.重复的DNA序列 本题就是找到长度为10的字符出现次数大于2的 子串序列 方法&#xff1a;使用哈希表 class Solution {public List<String> findRepeatedDnaSequences(String s) {List<String> ans new ArrayList<String>();HashMap<String,Integer&g…

GitLab下载地址是127.0.0.1如何修改ip

问题&#xff1a; 在下图位置之前我的ip是127.0.0.1&#xff0c;那我是如何修改的呢&#xff1f;请看下文 解决方案&#xff1a; 配置 GitLab站点 Url和端口号 GitLab 默认的配置文件路径是 /etc/gitlab/gitlab.rb # 修改配置文件 $ sudo vi /etc/gitlab/gitlab.rb 默认的站…

【lesson14】MySQL表的基本查询retrieve(读取)1

文章目录 表的基本操作介绍retrieveselect列建表基本测试 where子句建表基本测试 表的基本操作介绍 CRUD : Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#xff0c;Delete&#xff08;删除&#xff09; retrieve select列 建表 基本测试 插入数据 全列查询 …

【人工智能】实验三 A*算法求解八/十五数码问题实验与基础知识

实验三 A*算法求解八数码问题实验 实验目的 熟悉和掌握启发式搜索的定义、估价函数和算法过程&#xff0c;并利用A*算法求解N数码难题&#xff0c;理解求解流程和搜索顺序。 实验内容 以8数码问题和15数码问题为例实现A*算法的求解程序&#xff08;编程语言不限&#xff09…

Redis知识详解(超详细)

1. 背景 Redis是由意大利人Antirez&#xff08;Salvatore Sanfilippo&#xff09;在2009年创造的开源内存数据结构存储系统。Redis的名字来自意大利语“Repubblica di Redis”&#xff0c;意思是“基于字典的共和国”。它是一个基于内存的键值对存储系统&#xff0c;具有快速、…

Leetcode 491 递增子序列

题意理解&#xff1a; 输入&#xff1a;nums [4,6,7,7] 输出&#xff1a;[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]] 这里不止要找一个子序列&#xff0c;还要元素保证其在原来的集合中的前后顺序&#xff0c;且应为增序。 为保证一个增序序列&#xff0c;…

移动端Vant中的Calendar日历增加显示农历(节日、节气)功能

核心&#xff1a; 使用 js-calendar-converter 库实现 npm地址&#xff1a;js-calendar-converter 内部使用原生calendar.js&#xff0c; 中国农历&#xff08;阴阳历&#xff09;和西元阳历即公历互转JavaScript库&#xff0c;具体实现感兴趣的可自行查看其实现源码。 原日…

【人工智能】实验四:遗传算法求函数最大值实验与基础知识

实验四&#xff1a;遗传算法求函数最大值实验 实验目的 熟悉和掌握遗传算法的原理、流程和编码策略&#xff0c;并利用遗传算法求解函数优化问题&#xff0c;理解求解流程并测试主要参数对结果的影响。 实验内容 采用遗传算法求解函数最大值。 实验要求 1. 用遗传算法求解…

扁平化菜单功能制作

网页效果&#xff1a; HTML部分&#xff1a; <body><ul class"nav"><li><a href"javascript:void(0);">菜单项目一</a><ul><li>子菜单项01</li><li>子菜单项02</li><li>子菜单项03<…

【C++】optional的使用(一)

这篇文章介绍下C17引入的std::optional 为什么要有 optional 一般来说&#xff0c;如果想要一个函数返回“多个”值&#xff0c;C程序员倾向于使用结构体/类完成这个操作。即定义一个通用的结构体&#xff0c;在函数内部完成装填&#xff0c;然后返回一个实例化的结构体。 #…

解决PP材质粘合问题用PP专用UV胶水

PP材料已经广泛应用于各行各业&#xff0c;在粘接中会有不同的问题需求&#xff0c;那么使用专用于PP的UV胶水可能是解决PP材质粘合问题的一种有效方法。 主要在于&#xff1a;UV胶水在紫外线照射下可以快速固化&#xff0c;形成坚固的连接。所以使用PP专用UV胶水时可以考虑&am…

oracle Job 定时任务

目录 介绍&#xff1a; 优点&#xff1a; 缺点&#xff1a; 使用场景&#xff1a; --查看定时任务 --查询存储过程 案例&#xff1a; --创建一个名为t_oracle_job的表 在创建定时任务时&#xff0c;各个参数的含义如下&#xff1a; 1. job_name&#xff1a…

day02-报表技术POI

1、基于模板导出列表数据 1.1、需求 按照以下样式导出excel 1.2、思路 首先准备一个excel模板&#xff0c;这个模板把复杂的样式和固定的内容先准备好并且放入到项目中&#xff0c;然后读取到模板后向里面放入数据。 1.3、实现 第一步&#xff1a;准备一个excel作为导出的…

【ArkTS】入门

代码结构分析 struct Index{ } 「自定义组件&#xff1a;可复用的UI单元」 xxx 「装饰器&#xff1a;用来装饰类结构、方法、变量」 Entry 标记当前组件是入口组件&#xff08;该组件可被独立访问&#xff0c;通俗来讲&#xff1a;它自己就是一个页面&#xff09;Component 用…

LeetCode-克服链表不能随机访问的问题

1.重排链表 题目描述&#xff1a; 给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff1a; L0 → L1 → … → Ln - 1 → Ln 请将其重新排列后变为&#xff1a; L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 不能只是单纯的改变节点内部的值&#xff0…

解决vue3+ts打包,ts类型检查报错导致打包失败

最近拉的开源大屏项目goview&#xff0c;在打包的过程中一直报Ts类型报错导致打包失败&#xff0c;项目的打包命令为&#xff1a; "build": "vue-tsc --noEmit && vite build" 是因为 vue-tsc --noEmit 是 TypeScript 编译器&#xff08;tsc&#…