【C++】二叉搜索树

🔥个人主页🔥:孤寂大仙V

🌈收录专栏🌈:C++从小白到高手

🌹往期回顾🌹:【C++】多态

🔖 流水不争,争的是滔滔不息

文章目录


一、二叉搜索树的概念

二叉搜索树(Binary Search Tree, BST)又称二叉排序树是一种二叉树,满足以下性质:

  1. 节点的左子树上所有节点的值都小于该节点的值。
  2. 节点的右子树上所有节点的值都大于该节点的值。
  3. 左右子树也必须是二叉搜索树(递归定义)。

⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义,后续我

们学习map/set/multimap/multiset系列容器底层就是⼆叉搜索树,其中map/set不⽀持插⼊相等

值,multimap/multiset⽀持插⼊相等值。

二、搜索二叉树的操作

二叉搜索树的插入

  1. 树为空,则直接新增结点,赋值给root指针
  2. 树不空,按⼆叉搜索树性质,插⼊值⽐当前结点⼤往右⾛,插⼊值⽐当前结点⼩往左⾛,找到空位置,插⼊新结点。
  3. 如果⽀持插⼊相等的值,插⼊值跟当前结点相等的值可以往右⾛,也可以往左⾛,找到空位置,插⼊新结点。(要注意的是要保持逻辑⼀致性,插⼊相等的值不要⼀会往右⾛,⼀会往左⾛)
cpp 复制代码
bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		// 如果树为空,直接创建根节点。
		_root = new Node(key);
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;

	// 找到适合的插入位置。
	while (cur)
	{
		if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			// 如果发现相同的键值,则插入失败。
			return false;
		}
	}

	// 创建新节点并插入到合适的位置。
	cur = new Node(key);
	if (parent->_key > key)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}

	return true;
}

二叉搜索树的查找

  1. 从根开始⽐较,查找x,x⽐根的值⼤则往右边⾛查找,x⽐根值⼩则往左边⾛查找。
  2. 最多查找⾼度次,⾛到到空,还没找到,这个值不存在。
  3. 如果不⽀持插⼊相等的值,找到x即可返回。
  4. 如果⽀持插⼊相等的值,意味着有多个x存在,⼀般要求查找中序的第⼀个x。如下图,查找3,要找到1的右孩⼦的那个3返回。
cpp 复制代码
bool Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)
		{
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			cur = cur->_left;
		}
		else
		{
			return true;
		}
	}

	return false;
}

二叉搜索树的删除

⾸先查找元素是否在⼆叉搜索树中,如果不存在,则返回false。

如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)

1. 要删除结点N左右孩⼦均为空
2. 要删除的结点N左孩⼦位空,右孩⼦结点不为空
3. 要删除的结点N右孩⼦位空,左孩⼦结点不为空
4. 要删除的结点N左右孩⼦结点均不为空

对应以上四种情况的解决⽅案:

  1. 把N结点的⽗亲对应孩⼦指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是⼀样的)
  2. 把N结点的⽗亲对应孩⼦指针指向N的右孩⼦,直接删除N结点
  3. 把N结点的⽗亲对应孩⼦指针指向N的左孩⼦,直接删除N结点
  4. ⽆法直接删除N结点,因为N的两个孩⼦⽆处安放,只能⽤替换法删除。找N左⼦树的值最⼤结点R(最右结点)或者N右⼦树的值最⼩结点R(最左结点)替代N,因为这两个结点中任意⼀个,放到N的位置,都满⾜⼆叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转⽽变成删除R结点,R结点符合情况2或情况3,可以直接删除。
cpp 复制代码
bool Erase(const K& key)
{
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			if (cur->_left == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
				}

				delete cur;

			}
			else if (cur->_right == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->_left;
				}
				else
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}
				delete cur;
			}
			else
			{
				Node* replaceParent = cur;
				Node* replace = cur->_right;
				while (replace->_left)
				{
					replaceParent = replace;
					replace = replace->_left;
				}

				cur->_key = replace->_key;

				if (replaceParent->_left == replace)
					replaceParent->_left = replace->_right;
				else
					replaceParent->_right = replace->_right;

				delete replace;
			}
			return true;
		}
	}

	return false;
}

三、二叉搜索树的性能分析

那么这样的效率显然是⽆法满⾜我们需求的,我们后续课程需要继续讲解⼆叉搜索树的变形,平衡⼆叉搜索树AVL树和红⿊树,才能适⽤于我们在内存中存储和搜索数据。

四、二叉搜索树的实现代码

cpp 复制代码
#include<iostream>
using namespace std;
namespace key
{
	template<class K>
	struct BSNode
	{
		K _key;
		BSNode<K>* _left;
		BSNode<K>* _right;

		BSNode(const K& key)
			: _key(key)
			, _left(nullptr)
			, _right(nullptr)
		{}		
	};

	template<class K>
	
	class BSTree
	{
		using Node = BSNode<K>;
	public:
		//插入
		bool Insert(const K& key)
		{
			if (_root == nullptr)
			{
				// 如果树为空,直接创建根节点。
				_root = new Node(key);
				return true;
			}

			Node* parent = nullptr;
			Node* cur = _root;

			// 找到适合的插入位置。
			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					// 如果发现相同的键值,则插入失败。
					return false;
				}
			}

			// 创建新节点并插入到合适的位置。
			cur = new Node(key);
			if (parent->_key > key)
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}

			return true;
		}

		bool Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key < key)
				{
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else
				{
					return true;
				}
			}

			return false;
		}

		bool Erase(const K& key)
		{
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					if (cur->_left == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}
						}

						delete cur;

					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}
						delete cur;
					}
					else
					{
						Node* replaceParent = cur;
						Node* replace = cur->_right;
						while (replace->_left)
						{
							replaceParent = replace;
							replace = replace->_left;
						}

						cur->_key = replace->_key;

						if (replaceParent->_left == replace)
							replaceParent->_left = replace->_right;
						else
							replaceParent->_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;

	};
}

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

key搜索场景

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

场景1:⼩区⽆⼈值守⻋库,⼩区⻋库买了⻋位的业主⻋才能进⼩区,那么物业会把买了⻋位的业主的⻋牌号录⼊后台系统,⻋辆进⼊时扫描⻋牌在不在系统中,在则抬杆,不在则提⽰⾮本⼩区⻋辆,⽆法进⼊。

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

key/value搜索场景:

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

场景1:简单中英互译字典,树的结构中(结点)存储key(英⽂)和vlaue(中⽂),搜索时输⼊英⽂,则同时查找到了英⽂对应的中⽂。

场景2:商场⽆⼈值守⻋库,⼊⼝进场时扫描⻋牌,记录⻋牌和⼊场时间,出⼝离场时,扫描⻋牌,查找⼊场时间,⽤当前时间-⼊场时间计算出停⻋时⻓,计算出停⻋费⽤,缴费后抬杆,⻋辆离场。

场景3:统计⼀篇⽂章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。

六、key/value⼆叉搜索树代码实现

cpp 复制代码
namespace key_value
{
	template<class K, class V>
	struct BSTNode
	{
		K _key;
		V _value;

		BSTNode<K, V>* _left;
		BSTNode<K, V>* _right;

		BSTNode(const K& key, const V& value)
			:_key(key)
			, _value(value)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

	// Binary Search Tree
	// Key/value
	template<class K, class V>
	class BSTree
	{
		//typedef BSTNode<K> Node;
		using Node = BSTNode<K, V>;
	public:
		// 强制生成构造
		BSTree() = default;

		BSTree(const BSTree& t)
		{
			_root = Copy(t._root);
		}

		BSTree& operator=(BSTree tmp)
		{
			swap(_root, tmp._root);
			return *this;
		}

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

		bool Insert(const K& key, const V& value)
		{
			if (_root == nullptr)
			{
				_root = new Node(key, value);
				return true;
			}

			Node* parent = nullptr;
			Node* cur = _root;

			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;
				}
			}

			cur = new Node(key, value);
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}

		Node* Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key < key)
				{
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else
				{
					return cur;
				}
			}

			return nullptr;
		}

		bool Erase(const K& key)
		{
			Node* parent = nullptr;
			Node* cur = _root;

			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					// 删除
					// 左为空
					if (cur->_left == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}
						}
						delete cur;

					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							// 右为空
							if (parent->_left == cur)
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}

						delete cur;

					}
					else
					{
						// 左右都不为空
						// 右子树最左节点
						Node* replaceParent = cur;
						Node* replace = cur->_right;
						while (replace->_left)
						{
							replaceParent = replace;
							replace = replace->_left;
						}

						cur->_key = replace->_key;

						if (replaceParent->_left == replace)
							replaceParent->_left = replace->_right;
						else
							replaceParent->_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 << ":" << root->_value << endl;
			_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* newRoot = new Node(root->_key, root->_value);
			newRoot->_left = Copy(root->_left);
			newRoot->_right = Copy(root->_right);
			return newRoot;
		}
	private:
		Node* _root = nullptr;
	};
}
相关推荐
此木|西贝4 分钟前
【设计模式】原型模式
java·设计模式·原型模式
可乐加.糖21 分钟前
一篇关于Netty相关的梳理总结
java·后端·网络协议·netty·信息与通信
懒羊羊大王&22 分钟前
模版进阶(沉淀中)
c++
无名之逆23 分钟前
Rust 开发提效神器:lombok-macros 宏库
服务器·开发语言·前端·数据库·后端·python·rust
s91236010124 分钟前
rust 同时处理多个异步任务
java·数据库·rust
9号达人24 分钟前
java9新特性详解与实践
java·后端·面试
cg501728 分钟前
Spring Boot 的配置文件
java·linux·spring boot
似水এ᭄往昔29 分钟前
【C语言】文件操作
c语言·开发语言
啊喜拔牙36 分钟前
1. hadoop 集群的常用命令
java·大数据·开发语言·python·scala
owde1 小时前
顺序容器 -list双向链表
数据结构·c++·链表·list