【C++】二叉搜索树的底层思想 and 大厂常问内容

一、概念

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

        1)若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值。

        2)若它的右子树不为空,则右子树上所有结点的值都大于等于根结点的值。

        3)它的左右子树也分别为二叉搜索树。

        4)二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义,后续我们学习map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等值,multimap/multiset支持插入相等值。

 二、性能分析

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其高度为:log2N

最差情况下,二叉搜索树退化为单支树(或者类似单支),其高度为:N

所以综合而言二叉搜索树增删查改时间复杂度为:O(N),那么这样的效率显然是无法满足我们需求的,后面博客中的平衡二叉搜索树AVL树和红黑树,才能适用于我们在内存中存储和搜索数据。另外需要说明的是,二分查找也可以实现O(log2N)级别的查找效率,但是二分查找有两大缺陷:

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

2.插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据一般需要挪动数据。这里也就体现出了平衡二叉搜索树的价值。

三、搜索二叉树的底层思想和模拟实现

1)插入

插入的具体过程如下:

1、树为空,则直接新增结点,赋值给root指针

2、树不空,按二叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。

3、如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。(要注意的是要保持逻辑一致性,插入相等的值不要一会往右走,一会往左走)

代码示例:

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->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}

	return true;
}

 注意:这里的代码只实现插入的过程,结点的定义和二叉搜索树的定义还没有实现,后面的完整代码会具体实现。

2)查找

流程:

1、从根开始比较,查找x,x比根的值大则往右边走查找,x比根值小则往左边走查找。

2、最多查找高度次,走到到空,还没找到,这个值不存在。

3、如果不支持插入相等的值,找到x即可返回4;如果支持插入相等的值,意味着有多个x存在,一般要求查找中序的第一个x。

代码示例:

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

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

		return false;
	}

3)删除

        首先查找元素是否在二叉搜索树中,如果不存在,则返回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,可以直接删除。

思想风暴:

代码示例:

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

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

补充知识:如果我们显示写了构造函数,就不会有默认构造,C++11新增了一个关键字default,这个关键字可以在强制生成默认构造。

代码示例:

template<class K>
class BSTree
{

	typedef BSTreeNode<K> Node;
public:

	BSTree() = default;//强制生成默认构造
	BSTree(const BSTree<K>& t)
	{
		_root = Copy(t._root);
	}

	Node* _root = nullptr;
};

类模板在外面要带参数,在类里面可以不带参数,相当于是一种特殊处理:

	BSTree(const BSTree<K>& t)//也可以这么写  BSTree(const BSTree& t)
	{
		_root = Copy(t._root);
	}

四、二叉搜索树的使用场景

         1、key搜索场景

         小区判断车是否本小区的居民的车:搜索车的车牌是否是本小区的。

        检查单词是否输入正确:用户输入单词,根据单词的拼写搜索正确的单词,并且和用户写的单词进行匹配。

        2、key和value的搜索场景

       小区收停车费:把车的停车的时间和车牌记录下来,等车走的时候根据车牌搜索车停车时间进行收费。

        中英互译:根据用户输入的中文或者英文找对对应的英文或者中文。

五、完整代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;


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

	K _key;

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

};

template<class K>
class BSTree
{

	typedef BSTreeNode<K> Node;
public:

	BSTree() = default;//强制生成默认构造
	BSTree(const BSTree<K>& t)//也可以这么写  BSTree(const BSTree& t)
	{
		_root = Copy(t._root);
	}


	~BSTree()
	{
		Destory(_root);
		_root = nullptr;
		cout << "~BSTree()" << endl;
	}

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

		return *this;
	}
	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->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		return true;
	}

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

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

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

		return false;
	}

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

		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* 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;
	}
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* copy = new Node(root->_key);
		copy->_left = Copy(root->_left);
		copy->_left = Copy(root->_right);

		return copy;
	}

	Node* _root = nullptr;
};

完!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值