【C++】二叉搜索树

发布于:2025-06-06 ⋅ 阅读:(22) ⋅ 点赞:(0)

在这里插入图片描述

文章目录

  • 一、概念与性能分析
  • 二、二叉搜索树的实现
    • 1. 结点
    • 2. 默认成员函数
    • 3. 插入
    • 4. 查找
    • 5. 删除
    • 6. 中序遍历
    • 7. 完整实现
  • 三、key和key/value

一、概念与性能分析

二叉搜索树,又称搜索二叉树、二叉排序树,是一种具有特殊性质的二叉树:

  • 左子树上所有结点的值都小于等于根结点的值
  • 右子树上所有结点的值都大于等于根结点的值
  • 它的左右子树也都是二叉搜索树
  • 二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看场景需求

由于它本身和它的子树都是二叉搜索树,不难想象:二叉搜索树的中序遍历一定是升序序列

从名字就能看出来,二叉搜索树主要用于搜索。最优情况下,如果它接近完全二叉树的结构,不难分析,最大搜索次数即其树的高度:log2N;最差情况下,如果它接近单支树的结构,则其搜索次数接近结点个数:N。在这里插入图片描述
所以综合来看二叉搜索树的增删查改效率为O(N),这样的时间复杂度还是太高了。必须将二叉搜索树的结构进一步优化,也就是以后要学习的平衡二叉搜索树AVL树、红黑树。

二、二叉搜索树的实现

1. 结点

首先需要定义出结点的结构:

template<class K>
struct BSTreeNode
{
	BSTreeNode* _left;
	BSTreeNode* _right;
	K _key;

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

2. 默认成员函数

由于树的类中涉及到结点资源的开辟和删除,所以需要我们自己写拷贝构造、赋值重载、析构函数等。

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
private:
	Node* _root;

public:
//......
}
  • 默认构造函数:
//默认构造一棵空树
BSTree()
	:_root(nullptr)
{ }

之前我们学习链式结构的树,它的很多操作都需要用递归完成,但我们这里的拷贝构造、析构函数没办法传参递归怎么办?我们可以写出递归的函数后,“包装”起来:

  • 拷贝构造函数:
BSTree(const BSTree<K>& t)
{
    //实际完成拷贝功能的是Copy函数,我们的拷贝构造只是把他包装起来
	_root = Copy(t._root);
}

Node* Copy(Node* root)
{
	if (root == nullptr)
	{
		return nullptr;
	}
	Node* newnode = new Node(root->_key);
	//利用递归完成所有结点的拷贝
	newnode->_left = Copy(root->_left);
	newnode->_right = Copy(root->_right);

	return newnode;
}
  • 析构函数:
~BSTree()
{
	Destory(_root);
	_root = nullptr;
}

void Destory(Node* root)
{
	if (root == nullptr)
	{
		return;
	}
	//利用递归完成所有结点的资源释放
	Destory(root->_left);
	Destory(root->_right);
	delete root;
}

拷贝构造函数的Copy函数和析构函数的Destory函数仅供它们内部使用,所以可以把它们放到private部分

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

3. 插入

二叉搜索树的插入要按照搜索树的性质寻找正确位置插入:

  • 树为空,则直接新增结点作为root
  • 树不为空,则按照二叉搜索树性质,插入值比当前节点值大就往右走,插入值比当前节点值小则往左走。直到找到空位置,插入新节点:

在这里插入图片描述
代码实现:

bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
    //parent保存待插入位置的父结点
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (key < cur->_key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (key > cur->_key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			//如果key值已存在,则插入失败
			return false;
		}
	}

	//cur找到了正确的位置
	cur = new Node(key);
	if (parent->_key < key)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	return true;
}

4. 查找

查找一个值是否存在,也很简单了:

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

5. 删除

二叉搜索树的删除相对复杂,首先查找待删除元素结点,不存在则返回false。
若存在,则显然有以下几种情形:(假设要删除的结点为x)
x没有孩子、有左孩子或右孩子、有两个孩子。
为了删除结点后不破坏搜索二叉树的性质,它们要用不同的解决方法:

  • 若x没有孩子,则直接删除x
  • 若x只有一个孩子,则让x的父亲的原指向x的指针改为指向x的孩子,再删除x
  • 若x有两个孩子,使用替换法:找x的左子树的最大结点l(即左子树的最右结点),或x的右子树的最小结点r(即右子树的最左结点),代替x。因为这两个结点的值替换x后,都还能保持搜索二叉树的性质。最后再删除l或r结点,因为l或r结点一定是没有孩子或只有一个孩子的,再用上面两种情形方法处理就好。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码实现,可以将情形1和情形2整合讨论:

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

		else //找到了待删除结点cur,开始删除
		{

			//如果cur左孩子为空,就连接parent和cur的右子
			if (cur->_left == nullptr)
			{
				//特殊情况cur为根结点,cur右孩子当根结点
				if (cur == _root)
				{
					_root = cur->_right;
				}

				else
				{
					//如果cur是parent的左,则让parent的左指向cur的右子
					if (cur == parent->_left)
					{
						parent->_left = cur->_right;
					}
					//如果cur是parent的右,则让parent的右指向cur的右子
					else
					{
						parent->_right = cur->_right;
					}
	
				}

				delete cur;
			}

			//如果cur右孩子为空,就连接parent和cur的左子
			else if (cur->_right == nullptr)
			{
				//特殊情况cur为根结点,cur左孩子当根结点
				if (cur == _root)
				{
					_root = cur->_left;
				}

				else
				{
					//如果cur是parent的左,则让parent的左指向cur的左子
					if (cur == parent->_left)
					{
						parent->_left = cur->_left;
					}
					//如果cur是parent的右,则让parent的右指向cur的左子
					else
					{
						parent->_right = cur->_left;
					}

				}

				delete cur;
			}


			else //两个孩子,这里选择找左子树的最大结点替换
			{
				Node* LeftMaxParent = cur;
				Node* LeftMax = cur->_left;
				//找左子树的最右结点
				while (LeftMax->_right)
				{
					LeftMaxParent = LeftMax;
					LeftMax = LeftMax->_right;
				}
				swap(cur->_key, LeftMax->_key);

				//此时LeftMax一定没有右孩子,可能有左孩子
				//还可能LeftMaxParent就是根结点,所以要判断LeftMax是其父的左还是右孩子                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
				if (LeftMaxParent->_left == LeftMax)
				{
					LeftMaxParent->_left = LeftMax->_left;
				}
				else
				{
					LeftMaxParent->_right = LeftMax->_left;
				}

				delete LeftMax;
			}

			return true;

		}
	}

	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);
}

同样地,将_InOrder函数放在private中。

7. 完整实现

综上,二叉搜索树的完整实现:

namespace lydly
{
	template<class K>
	struct BSTreeNode
	{
		BSTreeNode* _left;
		BSTreeNode* _right;
		K _key;

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


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

	private:
		Node* _root;

	public:
		//默认构造一棵空树
		BSTree()
			:_root(nullptr)
		{ }

		//拷贝构造
		BSTree(const BSTree<K>& t)
		{
			_root = Copy(t._root);
		}
		
		BSTree<K>& operator=(BSTree<K> t)
		{
			swap(_root, t._root);

			return *this;
		}

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

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

			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (key > cur->_key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					//如果key值已存在,则插入失败
					return false;
				}
			}

			//cur找到了正确的位置
			cur = new Node(key);
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			return true;
		}

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

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

				else //找到了待删除结点,开始删除
				{

					//如果cur左孩子为空,就连接parent和cur的右子
					if (cur->_left == nullptr)
					{
						//特殊情况cur为根结点,cur右子当根
						if (cur == _root)
						{
							_root = cur->_right;
						}

						else
						{
							//如果cur是parent的左,则让parent的左指向cur的右子
							if (cur == parent->_left)
							{
								parent->_left = cur->_right;
							}
							//如果cur是parent的右,则让parent的右指向cur的右子
							else
							{
								parent->_right = cur->_right;
							}
	
						}

						delete cur;
					}

					//如果cur右孩子为空,就连接parent和cur的左子
					else if (cur->_right == nullptr)
					{
						//特殊情况cur为根结点,cur左子当根
						if (cur == _root)
						{
							_root = cur->_left;
						}

						else
						{
							//如果cur是parent的左,则让parent的左指向cur的左子
							if (cur == parent->_left)
							{
								parent->_left = cur->_left;
							}
							//如果cur是parent的右,则让parent的右指向cur的左子
							else
							{
								parent->_right = cur->_left;
							}

						}

						delete cur;
					}


					else //两个孩子,这里选择找左子树的最大结点替换
					{
						Node* LeftMaxParent = cur;
						Node* LeftMax = cur->_left;
						//找左子树的最右结点
						while (LeftMax->_right)
						{
							LeftMaxParent = LeftMax;
							LeftMax = LeftMax->_right;
						}
						swap(cur->_key, LeftMax->_key);

						//此时LeftMax一定没有右孩子,可能有左孩子
						//还可能LeftMaxParent就是根结点,所以要判断LeftMax是其父的左还是右孩子                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
						if (LeftMaxParent->_left == LeftMax)
						{
							LeftMaxParent->_left = LeftMax->_left;
						}
						else
						{
							LeftMaxParent->_right = LeftMax->_left;
						}

						delete LeftMax;
					}

					return true;

				}
			}

			return false;
		}

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


	private:
		void Destory(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			Destory(root->_left);
			Destory(root->_right);
			delete root;
		}


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

		Node* Copy(Node* root)
		{
			if (root == nullptr)
			{
				return nullptr;
			}
			Node* newnode = new Node(root->_key);
			//利用递归完成所有结点的拷贝
			newnode->_left = Copy(root->_left);
			newnode->_right = Copy(root->_right);

			return newnode;
		}
		
	};
} 

简单测试:
在这里插入图片描述
没有问题。

三、key和key/value

上面我们实现的二叉搜索树,结点中只存储了一个key,称为关键码,关键码即为需要搜索的值,使用这种二叉搜索树的场景只需要判断key是否存在。key的搜索场景实现的二叉搜索树支持增删查,不支持修改,因为修改key值破坏二叉搜索树性质了。

使用这种key二叉搜索树的场景,比如小区车库,只有业主的车能进入,车牌号作为key值,就在系统中检查key是否存在以确定是否为业主的车。

还有一种key/value的搜索场景:每一个关键码key,都有与之对应的value,value可以为任意类型对象,增删查还是以key为关键码在二叉搜索树中操作,修改不能改key,但是可以修改value。

使用这种key/value二叉搜索树的场景,比如英语词典,树的结构中存储key(英文)和value(中文),搜索时输入英文作为key进行查找,找到后输出对应的value中文。

key/value二叉搜索树的实现:

namespace lydly
{
	template<class K, class V>
	struct BSTreeNode
	{
		BSTreeNode<K, V>* _left;
		BSTreeNode<K, V>* _right;

		K _key;   
		V _value; 

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

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

	private:
		Node* _root = nullptr;

	public:
		
		BSTree()
		{ }

		BSTree(const BSTree<K, V>& t)
		{
			_root = Copy(t._root);
		}


		BSTree<K, V>& operator=(BSTree<K, V> t)
		{
			swap(_root, t._root);

			return *this;
		}

		~BSTree()
		{
			Destory(_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 (cur == parent->_left)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}
						}

						delete cur;
					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							if (cur == parent->_left)
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}

						delete cur;
					}
					else // 两个孩子
					{
						Node* pMinRight = cur;
						Node* minRight = cur->_right;
						while (minRight->_left)
						{
							pMinRight = minRight;
							minRight = minRight->_left;
						}

						swap(cur->_key, minRight->_key);
						if (pMinRight->_left == minRight)
						{
							pMinRight->_left = minRight->_right;
						}
						else
						{
							pMinRight->_right = minRight->_right;
						}

						delete minRight;
					}

					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 Destory(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

			Destory(root->_left);
			Destory(root->_right);
			delete root;
		}

		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;

			Node* copy = new Node(root->_key, root->_value);
			copy->_left = Copy(root->_left);
			copy->_right = Copy(root->_right);

			return copy;
		}
	};
	
}

本篇完,感谢阅读。


网站公告

今日签到

点亮在社区的每一天
去签到