搜索二叉树

目录

一、什么叫搜索二叉树

二、搜索二叉树的操作

1、Insert(插入)

2、Find(查找)

3、InOrder(中序遍历)

4、Erase(删除)

三、搜索二叉树的应用

1、key模型

2、key/value模型

四,搜索二叉树模拟实现


 

一、什么叫搜索二叉树

搜索二叉树又称二叉排序树或二叉查找树,若不为空,则有以下性质

搜索二叉树有下面三条性质:

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

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

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

 上图就是典型的搜索二叉树,根节点6,它的左子树上所有值都小于它,右子树所有值都大于它,并且左右字树也各自符合这个性质

如果想找一个数,最多查找高度次就可以找到


二、搜索二叉树的操作

下面的模拟实现中,代码有详细注释如何实现的

还有递归实现,都在下面的模拟实现的代码中

1、Insert(插入)

先和根结点相比,大于根结点再与根结点的右子树比,小于则跟左子树比,以此类推插入到合适的地方

2、Find(查找)

先和根结点相比,判断大小后再与左或右子树比,直到找到或未找到结束

3、InOrder(中序遍历)

递归调用,先左子树,再根结点,再右子树

4、Erase(删除)

①删除结点是叶子结点,例如图中的结点3

这里的情况①可以和情况②合并,不论删除结点是叶子结点还是有一个孩子,都可以将删除结点的父结点指向删除结点的子节点,情况①即指向空

所以就不需要判断删除结点是否是叶子结点了,代码更简洁些

②删除结点只有一个孩子,例如图中的结点2,可以删除结点2后,将结点4直接指向被删除结点的那个子结点,即删除结点的左为空父亲指向它的右孩子,右为空父亲指向它的左孩子

这里还会有两种在父结点左还是右的情况:

第一种:

 

要删除cur结点,cur结点在parent结点的,且cur结点的右为空,则parent的左指针指向结点4

 第二种:

 要删除cur结点,cur结点在parent结点的,且cur结点的右为空,则parent的右指针指向结点2

这两种情况说明,在删除结点只有一个孩子的情况下,代码首先需要判断该删除结点在他它父结点的左边还是右边,不同的情况处理不同

最后还有一种特殊情况也要考虑到:删除的是root结点,这时的parent就不能访问了,直接改变root即可 

③若删除结点有两个孩子,则用替换法删除,比如需要删除结点3

删除结点是有两个孩子的,这时我们就需要用到替换法进行删除

替换方法是:替换删除结点的左子树的最大结点,或右子树最小的结点与该结点交换,然后删除替换结点

选择左子树的最大结点替换理由:删除结点的左子树本身都小于该结点,因此将左子树最大结点替换后,除去替换结点,它的左子树依旧小于该结点,也小于右子树的所有结点;并且作为左子树的最大结点也说明了该结点在左子树的最右边(搜索二叉树中右边始终大于左边),所以该结点要么没有孩子,要么只有一个孩子,符合了上面的①②条件,可以解决;以此类推,右子树最小的结点与该结点交换也成立

即如下图所示,若要删除结点3:

结点2作为结点3:左子树的最大结点,与结点3交换,然后删除结点3,变为下图:

删除结点后仍满足搜索二叉树的性质 


三、搜索二叉树的应用

1、key模型

①小区的门禁卡

每个人都有对应的key绑定,进出的时候,会在类似二叉搜索树的模型中找,匹配了就开门

②查看文章中的单词是否出错

词库中的单词都插入到一个搜索树中,每个单词与词库中的单词比对,从而完成需求

2、key/value模型

①简单的中英翻译程序

即在实现搜索二叉树时,模版中加一个class V,表示每个结点都存一个英文一个中文,除了原来的成员,再加一个V _value,插入时也插入V,之后在查找的时候查找原来的key,找到了就输出与之相关的value

②高铁站等车站,买完票刷身份证即可

买完票,你的身份证就会和这个票绑定起来,你刷身份证的时候,就会检测是否买票,是否是当前日期,当前的车次


四,搜索二叉树模拟实现

里面有详细注释帮助理解底层原理

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:
	//插入一个值为key的结点
	bool Insert(const K& key)
	{
		//先判断搜索二叉树是否为空
		if (_root == nullptr)
		{
			//若为空则直接开一个值为key的结点给根结点
			_root = new Node(key);
			//返回true,此次插入结束
			return true;
		}
		//定义一个需要插入位置的父结点,以便于插入后的连接
		Node* parent = nullptr;
		//定义一个需要插入位置cur
		Node* cur = _root;
		//循环用于找所需要插入的位置
		while (cur)
		{
			//与各个结点进行比较
			//若插入结点值大于该结点,则进入该结点的右子树
			if (cur->_key < key)
			{
				//每次cur进入下一位置前,都将当前位置给parent
				parent = cur;
				cur = cur->_right;
			}
			//若插入结点值大于该结点,则进入该结点的左子树
			else if (cur->_key > key)
			{
				//每次cur进入下一位置前,都将当前位置给parent
				parent = cur;
				cur = cur->_left;
			}
			//若出现与已有结点同大小的情况则不满足搜索二叉树
			//返回false
			else
			{
				return false;
			}
		}
		//new一个结点将key值代入,赋值给cur
		cur = new Node(key);
		//判断是在parent的左边还是右边,以便连接
		if (parent->_key > key)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		return true;
	}

	//查找函数
	//和Insert大体思路一样,从根结点往下比较
	//一层一层比较是否相同
	//相等则返回true
	//否则返回false
	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循环在二叉树中找删除结点
		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;
					cur = nullptr;
				}
				//删除结点的右为空
				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;
					cur = nullptr;
				}
				//删除结点左右都不为空
				//替换法删除(下面用右子树的最小结点写,两种都可)
				else
				{
					//min是右子树最小结点
					//minparent是右子树最小结点父结点
					Node* minparent = cur;
					Node* min = cur->_right;
					while (min->left)
					{						
						minparent = min;
						min = min->_left;
					}
					//删除结点cur与右子树最小结点min交换
					swap(cur->_key, min->_key);
					//判断min在minparent的左还是右
					if (minparent->_left == min)
					{
						//避免min的还有右孩子
						//这样的指向,不管有没有右孩子都对
						minparent->_left = min->_right;
					}
					else
					{
						minparent->_right = min->_right;
					}

					delete min;
				}
				//删除完毕
				return true;
			}
		}
		//二叉树全部找完,未找到待删结点
		return false;
	}

	//中序遍历打印,以便观察
	//中序的结果是升序
	//写一个InOrder函数调用_InOrder函数
	//可以调用到_root
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;

	}

	///
	//递归的写法
	bool 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);
	}

	~BSTree()
	{
		Destroy(_root);
	}

	//C++11用法:强制编译器生成默认的构造
	//此时自己写了拷贝构造,编译器不会生成默认构造
	BSTree() = default;

	BSTree(const BSTree<K>& t)
	{
		//_root接收返回的copyroot
		_root = _Copy(t._root);
	}

	//赋值 t2 = t1
	BSTree<K>& operator=(BSTree<K> t)
	{
		//t是t1的别名,而t1是我们所需要的
		//因此交换_root和t._root后,满足需求
		//并且出作用域交换后的t还会销毁,一举两得
		swap(_root, t._root);
		return *this;
	}

private:
	//配合拷贝构造
	Node* _Copy(Node* root)
	{
		//如果传入的是空指针,则返回空指针
		if (root == nullptr)
		{
			return nullptr;
		}
		//new一个copyroot,传入的root的key给它
		Node* copyroot = new Node(root->_key);
		//前序遍历,先根,再左,在右
		copyroot->_left = _Copy(root->_left);
		copyroot->_right = _Copy(root->_right);
		//遍历结束返回copyroot
		return copyroot;
	}

	//配合析构函数销毁
	void Destroy(Node*& root)
	{
		if (root == nullptr)
		{
			return;
		}

		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
		root = nullptr;
	}

	bool _FindR(Node* root, const K& key)
	{
		//递归到最后都没找到,返回false
		if (root == nullptr)
			return false;
		//如果找的值小于当前root值,递归左子树
		if (root->_key > key)
		{
			return _FindR(root->_left, key);
		}
		//如果找的值大于当前root值,递归右子树
		else if (root->_key < key)
		{
			return _FindR(root->_right, key);
		}
		//如果最终找到了,则返回true
		else
		{
			return true;
		}	
	}

	//这里的Node*& root,表示根结点指针的引用
	bool _InsertR(Node*& root, const K& key)
	{
		//Node*& root中的引用就用在这里
		//当找到需要插入的地方root时是nullptr
		//引用可以保证不是局部变量,以免运行结束被销毁
		//引用还可以保证这里的空指针是与它父结点连接着的
		//因为这里是递归调用的,上面传的就是父结点的左或右
		//将新结点插入后返回true,也就和父结点连接成功了
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		//如果找的值小于当前root值,递归左子树
		if (root->_key > key)
		{
			return _InsertR(root->_left, key);
		}
		//如果找的值大于当前root值,递归右子树
		else if (root->_key < key)
		{
			return _InsertR(root->_right, key);
		}
		//递归发现插入值出现过,返回false
		else
		{
			return false;
		}
	}

	//这里的Node*& root,和上面Insert的作用一样
	bool _EraseR(Node*& root, const K& key)
	{
		//找删除的结点到最后没找到,则返回false
		if (root == nullptr)
		{
			return false;
		}
		//如果找的值小于当前root值,递归左子树
		if (root->_key > key)
		{
			return _EraseR(root->_left, key);
		}
		//如果找的值大于当前root值,递归右子树
		else if (root->_key < key)
		{
			return _EraseR(root->_right, key);
		}
		//找到待删除的结点,开始删除
		else
		{
			//由于上面参数用了引用
			//即root=它的父结点的左或右指针指向结点的别名
			//所以若删除结点只有一个孩子的情况下
			//先判断它的左右孩子哪个不为空
			//然后给不为空的孩子链接起来
			//root是删除结点的父结点的左或右指针的别名
			//root->_right/_left是删除结点不为空的孩子
			//将它的父结点链接到不为空的孩子结点上
			//然后释放del就完成了删除
			Node* del = root;
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			//两个孩子都不为空的情况
			else
			{
				//找右子树的最左结点(最小)替换删除
				Node* min = root->_right;
				while (min->_left)
				{
					min = min->_left;
				}
				//交换删除结点与右树最小结点的key
				swap(root->_key, min->_key);
				//递归,删除交换后的删除结点右树的结点
				return _EraseR(root->_right, key);
			}
			delete del;
			return true;
		}
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}


	Node* _root = nullptr;
};

图片截图:

 

 

 


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值