二叉搜索树代码实现

目录​​​​​​​

一、概念:

二、代码实现二叉搜索树

1.  二叉搜索树的查找

 2. 二叉搜索树类的实现

3.  二叉搜索树的插入

4.  二叉搜索树的删除


一、概念:

        二叉搜索树:二叉搜索树又称二叉排序树,其具有以下特点:

1.可能为空树

2.其左子树的节点值均小于根节点的值

3.其右子树的节点值均大于根节点的值

4.其左右子树均构成二叉排序树

示例:

二、代码实现二叉搜索树

1.  二叉搜索树的查找

        假设我们要查找的值为6,假设要查找的值为key,由二叉搜索树的特点可知

        a. 从根节点开始进行查找,如果要查询的key值小于根节点,就进入左子树进行查找,如果要查询的key值大于根节点,就进入右子树进行查找。

        b.最多查找的次数为二叉搜索树的高度次,如果走到空的位置,则未查到,这个值不存在。

根据以上描述,我们根据上图对查找的过程如下:

        我们可以看出当key=6时,此时从根节点开始,key小于根节点的值,因此,要从根节点的左子树进行查找,此时到节点3处,可以得知key的值大于3,因此要在节点3的右子树进行查找。此时找到等于key的节点。

        上述过程用代码进行描述:

首先定义一个二叉搜索树节点的结构体:

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

	BSTreeNode(const K& val)
		:_left(nullptr)
		,_right(nullptr)
		,_val(val)
	{}

	Node* _left;
	Node* _right;
	K _val;
};

 2. 二叉搜索树类的实现

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

private:
	Node* _root = nullptr;    //将根节点初始化为空
};

        接下来根据上述描述实现查找功能函数:

	bool Find(const K& val)  // 传入要查找的key的值 val
	{

		Node* cur = _root;	//定义一个当前二叉搜索树的指针指向root
		if (cur == nullptr)	//如果当前指向root节点的指针为nullptr,表示当前树为空 返回false
		{
			return false;
		}
		while (cur)			//对树进行查找,如果为空,则表示未找到,退出循环
		{
			if (cur->_val < val)	//如果当前节点的值小于要查询的key值
			{
				cur = cur->_right;	//向当前节点的右子树进行查找
			}
			else if (cur->_val > val)	//如果当前节点的值大于要查询的key值
			{
				cur = cur->_left;	//向当前节点的左子树进行查找
			}
			else
			{
				return true;	//此时找到相等的节点
			}
		}

		return false;			//未找到返回false
	}

3.  二叉搜索树的插入

        a. 当前树如果为空树,即根节点为空(root==nullptr),则申请一个新的节点,并赋值给根节点

        b.如果树不为空,则根据二叉搜索树的性质找到合适的插入位置,插入新的节点。

假设我们要插入的节点为15,那么根据图我们可以表示为:

根据图示可以看出,如果要插入15这个节点,首先从根节点进行查找其合适的插入位置,根据左子树均小于根节点以及右子树大于根节点的性质,可以找到15的插入位置为节点14的右子树。

据图所示,当遍历二叉搜索树找合适的插入位置过程当中,结束条件为找到节点为空的位置,而当插入新节点的过程中,需要找到空节点的上一个位置链接新的节点,因此我们需要保存相关的节点。

代码实现如下:

	bool insert(const K& val)		//传入要插入的节点值
	{
		if (_root == nullptr)		//如果根节点为空,即空树,那么申请一个新的节点,并返回true
		{
			_root = new Node(val);
			return true;
		}

		Node* cur = _root;			//定义一个节点cur指向根节点
		Node* parent = nullptr;		//保存父节点,链接新的节点
		while (cur)					//如果cur为空,则代表找到了插入的位置
		{
			if (cur->_val < val)	//如果当前节点的值小于要查询的key值
			{
				parent = cur;		//更新父节点为当前节点的值
				cur = cur->_right;	//向右查找插入位置
			}
			else if (cur->_val > val)//如果当前节点的值大于要查询的key值
			{
				parent = cur;		//更新父节点为当前节点的值
				cur = cur->_left;	//向左查找插入位置
			}
			else
			{
				return false;		//如果找到了与要插入值相同的节点,那么就无法插入,返回false
			}
		}
		cur = new Node(val);		//这里已经找到了合适的位置,此刻申请一个新的节点赋值给cur

		if (parent->_val < val)		//此刻parent指向要插入的节点的父节点位置
		{
			parent->_right = cur;	//如果父节点的值小于要插入的值,则将其链接到右子树
		}
		else
		{
			parent->_left = cur;	//如果父节点的值小于要插入的值,则将其链接到左子树
		}

		return true;				//插入成功
			
	}

4.  二叉搜索树的删除

a. 首先查找当前二叉搜索树中是否存在要删除的节点,如果不存在则返回

b. 根据二叉搜索树的性质可知:二叉搜索树的左子树和右子树分别都是二叉搜索树,所以在删除节点的过程中应当始终保证二叉搜索树这一性质,因此删除节点分为以下几种情况:

(1) 删除叶子节点(即无孩子节点):直接进行删除,删除叶子节点并不会改变二叉搜索树其他节点的位置。

        如图所示,如果要删除的节点为叶子节点时,只需找到其所在位置对其进行删除即可,二叉搜索树其他节点依然构成二叉搜索树。只需将删除位置节点的父节点的指向置空即可。

(2)要删除的节点只有一个孩子节点:

        此处我们以14这个节点为例,其只有13这一个左孩子节点。由图可知,如果我们将14这个节点删除,那么我们需要将其左孩子节点13与其父节点10链接在一起,根据搜索树的性质,我们可以很清楚的看出应当将13链接到10的右孩子处。如下图所示:

        接下来我们看下一种情况,如图所示,如果我们此刻要删除的是3这个节点,那么就要将3的孩子节点连接到节点3的父节点,那么根据搜索树性质可知,左子树小于根节点,右子树大于根节点。那么节点3的右孩子节点6需要链接到父节点8的左孩子处。

因此会得到如下情况:

由以上两张图我们可以看出,同样都是删除只有一个孩子的节点,其操作是相同的。

只需将要删除节点的孩子节点链接至要删除节点的父节点处即可。

但是父节点在链接节点的时候,应该链接到左边还是右边呢?

        根据上图我们可以看出,如果我们要删除的节点是父节点的右节点,那么就将要删除节点的孩子连接到父节点的右孩子处,相反,如果我们要删除的节点是父节点的左孩子,那么就将要删除节点的孩子连接到父节点的左孩子处。

同时我们也可以看到,我们可以将(1)和(2)归类为同一种情况:

         在(1)中删除叶子节点的情况,最终都是将要删除节点的父节点连接至空,而(2)中情况是将要删除节点的父节点连接至其左孩子或右孩子处,此时(1)中的情况便会成为一种特殊情况。

(3)要删除的节点有两个孩子节点:

        如果要删除的节点既有左孩子又有右孩子,那么就无法使用上述的方式来进行删除,因为要始终保持二叉搜索树的左右子树均为二叉搜索树,即左子树均小于根节点,右子树均大于根节点。

        因此,我们可以采取如下方式进行删除此类节点,假设我们要删除的节点为3这个节点,其既有左孩子又有右孩子:

我们可以找到其右子树中最左侧的值,即其右子树中的最小值与其进行交换值:

此时我们只需删除4这个节点即可。方法如下:

a. 首先找到右子树最小的节点,我们定义为rightMin

b. 此时我们只需删除右子树中最小值这个节点即可

c. 我们所找到的是右子树中的最小节点,根据性质我们可以知道最小值节点应是右子树中最左的节点,因此最小值节点不会存在左孩子,但是有可能存在右孩子。

d.根据c描述,我们可以知道删除这个节点的时候遵循的内容是(2)中所述删除只有一个孩子节点时的情形,因此不做过多描述。

最终删除代码实现如下:

	bool erase(const K& val)
	{
		if (_root == nullptr)
		{
			return false;
		}
		Node* cur = _root;		//当前节点指向根节点
		Node* parent = nullptr;	//定义一个父节点

		while (cur)					//首先是查找要删除的节点,如果未找到则返回false
		{
			if (cur->_val < val)	//如果当前节点的值小于要删除的结点的值
			{
				parent = cur;		//保留父节点的位置
				cur = cur->_right;	//向右子树进行查找
			}
			else if (cur->_val > val)//如果当前节点的值大于要删除的结点的值
			{
				parent = cur;			//保留父节点的位置
				cur = cur->_left;		//向左子树进行查找
			}	
			else {							//此时代表找到了要删除的节点
				if (cur->_right == nullptr)	//如果当前节点的右孩子为空,则表示此时是要删除的节点只有一个孩子或者无孩子
				{
					if (cur == _root)		//如果此时节点为根节点
					{
						_root = cur->_left;	//要将根节点更新至左孩子处
					}
					else
					{
						if (cur == parent->_right)	//如果当前要删除节点为父节点的右孩子
						{
							parent->_right = cur->_left;	//将要删除节点的孩子链接至父节点的右孩子处
						}
						else {						//如果当前要删除节点为父节点的左孩子
							parent->_left = cur->_left;		//将要删除节点的孩子链接至父节点的左孩子处
						}
					}
			
					delete cur;						//删除节点
					return true;					
				}
				else if (cur->_left == nullptr)		//此处与上述思路相同,逻辑相反
				{
					if (cur == _root)
					{
						cur = cur->_right;
					}
					else
					{
						if (cur == parent->_right)
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;

						}
					}
				

					delete cur;
					return true;
				}
				else                                   // 此处代表要删除的节点不仅有左孩子也有右孩子,无法直接删除
				{

					Node* rightMin = cur->_right;		//定义一个右子树最小节点的指针
					Node* parent = _root;				//定义父节点
					while (rightMin->_left)				//查找右子树中值最小的节点
					{
						parent = rightMin;				//更新父节点
						rightMin = rightMin->_left;		
					}
					cur->_val = rightMin->_val;			//将右子树中最小节点的值赋值给要删除的节点
					if (rightMin == parent->_left)		// 此时只需删除右子树中最小值的节点即可,此时要删除的节点变为cur
						parent->_left = rightMin->_right;//	如果当前节点的值是父节点的左孩子,将当前节点cur的右孩子链接至父节点的左孩子
					else
						parent->_right = rightMin->_right;//如果当前节点的值是父节点的右孩子,将当前节点cur的右孩子链接至父节点右孩子
					delete rightMin;					//删除节点
					return true;
				}
			}
		}
		return false;
	}

  • 42
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值