【C++】搜索二叉树

📕 概念

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

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

请添加图片描述

📕 搜索二叉树的实现

框架

搜索二叉树也是一个二叉树,所以,要先用结构体定义树的节点,如下的 struct BSTreeNode,结构体里面包含左右指针以及值,当然了,也需要构造函数,因为 new 一个节点的时候是需要传参的。


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

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

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
    // 接口
private:
	Node* _root = nullptr;
};

插入节点

插入的分类如下:

  • 树为空,则直接新增节点,赋值给root指针
  • 树不空,按二叉搜索树性质查找插入位置,插入新节点

如下,当树不为空的时候,要找到需要插入节点的位置。

  • 如果当前节点的值,大于要插入的节点的值,那么当前节点往其左子节点移动。
  • 如果当前节点的值,小于要插入的节点的值,那么当前节点往其右子节点移动。
  • 如果当前节点的值,等于要插入的节点的值,说明该树里面已存在要插入的值的节点,插入失败返回 false。

但是,即使找到了需要插入的节点位置,也需要将其链接到父节点上,由于我们只找到了需要插入到哪个位置,并不知道其父节点,所以,需要定义一个指针变量 parent 来指向其父节点。

现在已经找到了需要插入的节点位置 cur ,以及其父节点 parent,其余信息我们是不知道的。比如,要插入的节点的值是比 parent 的值大还是小?所以这里需要判断一下,然后作链接。

bool Insert(const K& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(val);
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur) // 找到要插入的位置
		{
			if (cur->_key < val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else {
				return false;
			}
		}

		if (parent->_key < val)
			parent->_right = new Node(val);
		else
			parent->_left = new Node(val);

		return true;
	}

查找节点

查找节点很容易,当前节点的值比目标值大,那么当前节点就变成其左子节点;当前节点的值比目标值小,当前节点就变成其右子节点;否则就是相等,表示找到了。

当然,如果当前节点成了 nullptr ,那么就是没有找到。

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

★ 删除节点 ★

要删除节点,首先需要找到这个节点,找到之后,再考虑删除的事情。

删除的情况也分为多种,因为待删除节点挂接的情况有所不同

  • 要删除的结点无孩子结点
  • 要删除的结点只有左孩子结点
  • 要删除的结点只有右孩子结点
  • 要删除的结点有左、右孩子结点

但是,对于前三种情况,实际上可以合为一种类型。
如下,当要删除的节点无孩子节点,直接删除,然后父节点的对应子节点置为空。
当要删除的节点有一个子节点,那么把节点删除后,将被删除节点 的 非空的子节点链接到父节点。

可以归为一类的原因是,如下,删除 13 的时候,这个节点没有子节点,即左右子节点指针指向 nullptr,将 13 删除之后,父节点的左子节点也要指向 nullptr,这时可以理解为,将 13 的其中一个子节点链接到父节点的左子节点
请添加图片描述

看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:

  • 情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除
  • 情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除
  • 情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题–替换法删除

当要删除的节点,左右子树都存在的时候,删除起来是较为麻烦的。如下图,删除 3 这个节点,此时 3 存在左右子树,那么就需要找一个节点来替代 3 这个节点。同时,要使得替代 3 的节点,也要满足搜索二叉树的性质。
那么,就需要找到 3 的左子树中,最大的节点(该节点比 3 的左子树中其他节点都大,比 3 的右子树节点都小,适合替代 3 这个节点);或者是找到 3 的右子树中,最小的节点(理由和之前类似)。任意找这两个节点之一,替代 3 这个节点即可。以右子树的最小节点为例,下图中是 4 这个节点,但是 4 这个节点也有右子树(不会有左子树,如果有, 4 就不是最小节点),所以要将其右子树链接到 4 的父节点。

请添加图片描述

但是,如果右子树的最小节点,就是要删除节点的右节点呢?如下,例如要删除 8 ,此时去寻找 8 的右子树的最小节点,毫无疑问是 14 这个节点,此时需要单独判断一下,因为 parent 节点是 nullptr 。

请添加图片描述

如下,删除节点的代码。

	bool Erase(const K& val)
	{
		Node* cur = _root;
		Node* parent = _root;
		while (cur)
		{
			if (cur->_key < val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else // 相等,删除
			{
				// 左为空
				if (cur->_left == nullptr)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
					delete cur;
				}// 右为空
				else if (cur->_right == nullptr)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
					delete cur;
				}
				else // 左右都不为空
				{
					Node* pminRight = nullptr;
					Node* minRight=cur->_right;
					while (minRight->_left)
					{
						pminRight = minRight;
						minRight = minRight->_left;
					}

					cur->_key = minRight->_key;
					// 删除节点的右边就是最小值
					if (minRight == cur->_right)
					{
						cur->_right = minRight->_right;
					}
					else 
					{
						pminRight->_left = minRight->_right;
					}
					
					delete minRight;
				}
				return true;
			}

		}
		return false;
	}

📕 源代码

搜索二叉树代码如下,其递归法实现 插入、查找、删除等等,思路是一样的。


#pragma once
#include<algorithm>

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

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

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

	bool Insert(const K& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(val);
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_key < val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else {
				return false;
			}
		}

		if (parent->_key < val)
			parent->_right = new Node(val);
		else
			parent->_left = new Node(val);

		return true;
	}

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


	// 删除
	bool Erase(const K& val)
	{
		Node* cur = _root;
		Node* parent = _root;
		while (cur)
		{
			if (cur->_key < val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else // 相等,删除
			{
				// 左为空
				if (cur->_left == nullptr)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
					delete cur;
				}// 右为空
				else if (cur->_right == nullptr)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
					delete cur;
				}
				else // 左右都不为空
				{
					Node* pminRight = nullptr;
					Node* minRight=cur->_right;
					while (minRight->_left)
					{
						pminRight = minRight;
						minRight = minRight->_left;
					}

					cur->_key = minRight->_key;
					// 删除节点的右边就是最小值
					if (minRight == cur->_right)
					{
						cur->_right = minRight->_right;
					}
					else 
					{
						pminRight->_left = minRight->_right;
					}
					
					delete minRight;
				}
				return true;
			}

		}
		return false;
	}

	bool _FindR(Node* root, const K& val)
	{
		if (root == nullptr) return false;

		if (root->_key > val) return _FindR(root->_left, val);
		else if (root->_key < val) return _FindR(root->_right, val);
		else return true;
	}

	bool FIndR(const K& val)
	{
		return _FindR(_root, val);
	}

	bool _InsertR(Node* &root, const K& val)
	{
		if (root == nullptr)
		{
			root = new Node(val);
			return true;
		}
		if (root->_key < val)
			_InsertR(root->_right, val);
		else if (root->_key > val) 
			_InsertR(root->_left, val);
		else return false;
	}

	bool InsertR(const K& val)
	{
		return _InsertR(_root, val);
	}

	bool _EraseR(Node* &root, const K& val)
	{
		if (root == nullptr) return false;
		if (root->_key > val)
		{
			_EraseR(root->_left, val);
		}
		else if (root->_key < val)
		{
			_EraseR(root->_right, val);
		}
		else
		{
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else // 左右都不为空
			{
				Node* maxleft = root->_left;
				while (maxleft->_right)
				{
					maxleft = maxleft->_right;
				}
				swap(root->_key, maxleft->_key);
				_EraseR(root->_left, val);
			}
			return true;
		}

		return false;
	}

	bool EraseR(const K& val)
	{
		return _EraseR(_root, val);
	}

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

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

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

private:
	Node* _root = nullptr;
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力努力再努力.xx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值