C++ ——— 二叉搜索树

目录

二叉搜索树的概念

单个节点的结构

二叉搜索树的结构

中序遍历

插入

查找

删除

析构

拷贝构造

重载赋值运算符


二叉搜索树的概念

二叉搜索数也称为二叉排序数或者二叉查找树

二叉搜索树:一棵二叉树,可以为空;如果不为空,要满足以下性质

1. 非空左子树的所有键值小于其根节点的键值

2. 非空右子树的所有键值大于其根节点的键值

3. 左右子树都是二叉搜索树

特征:

这样的二叉搜索树通过中序遍历后是升序,因为左子树都小于根节点,右子树都大于根节点,所以也称之为二叉排序树 


单个节点的结构

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

	K _key;
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
};

左子树的值要小于根节点的值,右子树的值要大于根节点的值 


二叉搜索树的结构

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

public:
    // 函数实现...

private:
	Node* root = nullptr;
};

中序遍历

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

		// 左子树 - 根 - 右子树
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}

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

因为在类外面不能访问私有变量 _root,所以在类里面实现递归时需要配合子函数来实现


插入

bool Insert(const K& key)
{
	// 判断二叉树是否为空,为空时直接插入链接
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}

	// 不为空时,需要找到合适的插入位置
	Node* parent = nullptr;
	Node* cur = _root;

	while (cur != nullptr)
	{
		parent = cur;

		if (cur->_key < key)
		{
			// 当前节点的值小于要插入的值,那么就往右节点走
			cur = cur->_right;
		}
		else if(cur->_key > key)
		{
			// 否则就往左节点走
			cur = cur->_left;
		}
		else
		{
			// 相等时就结束
			return false;
		}
	}

	// 找到合适的位置时就链接
	cur = new Node(key);

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

	return true;
}

因为规定二叉搜索树不能出现一样的值,所有在判断是否相等时直接返回 false ,证明这个值已经存在了

parent 指针指向 cur 的前一个节点,方便最后 cur 找到合适的位置时链接,在链接时,parent 指向的节点也不知道 cur 该链接在左或是右,所以需要再次比较得出结果

测试代码:


查找

bool Find(const K& kay)
{
	Node* cur = _root;

	while (cur != nullptr)
	{
		if (cur->_key > kay)
		{
			// 当前节点大于 key 时就往左节点找
			cur = cur->_left;
		}
		else if (cur->_key < kay)
		{
			// 当前节点小于 key 时就往左节点找
			cur = cur->_right;
		}
		else
		{
			// 相等就是找到了,返回 true
			return true;
		}
	}

	// 一直没有找到就返回 false
	return false;
}

同样是根据左小右大的规则来查找


删除

bool Erase(const K& key)
{
	// 先找到要删除的节点
	Node* cur = _root;
	Node* parent = nullptr;

	while (cur != nullptr)
	{
		if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		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;
					}
				}
			}
			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;
					}
				}
			}
			else //当要删除的节点左右都不为空时
			{
				// 先找到右树的最小节点(最左节点)
				Node* subLeft = cur->_right;
				Node* subParent = cur;

				while (subLeft->_left != nullptr)
				{
					subLeft = subLeft->_left;
				}

				// 交换
				std::swap(cur->_key, subLeft->_key);

				// 链接
				if (subLeft == subParent->_left)
					subParent->_left = subLeft->_right;
				else
					subParent->_right = subLeft->_right;
			}

			// 删除完成,最后返回 true
			return true;
		}
	}

	// 没有找到要删除的节点
	return false;
}

如图所示:

先通过左小右大的原则来找要删除的节点,找到后开始删除

在删除的节点里面有三种情况:

1. 要删除的节点只有左子树没有右子树

2. 要删除的节点只有右子树没有左子树

3. 要删除的节点有左右子树

叶子节点可以归纳为1、2点内

那么先拿删除 14 节点举例:

那么 14 节点就是右子树为空,创建一个 parent 节点指针指向 14 节点的父亲,也就是 10 节点,并且直接判断 14 节点是 10 节点的左还是右,因为 14 节点的右为空,所以判断后 10 节点直接链接 14 节点的左即可

需要注意的是,当要删除的节点是根节点时,就是根节点的右或者左子树为空时,需要作特殊处理,详情请见代码

删除 10 节点,10 节点是左子树为空的情况也是一样的,这里就不过多赘述

删除叶子节点也和上面的代码逻辑一样

删除 8 节点举例:

8 节点既是根节点也是左右子树都不为空的情况,那么删除的话就需要使用交换法

找出左子树中最大的节点,或者找出右子树最小的节点进行交换,这样才能成功交换,并且不影响二叉树

以上代码中我实现的是找出右子树最小的节点进行交换,所以 subLeft 就是找到右子树中最小的那个节点,也就是最左边的那个节点,subParent 指向  subLeft 的父亲节点

然后 subLeft 和要删除的节点 cur 进行交换,交换后再通过 subParent 进行链接,因为 subLeft 是右子树的最左节点,所以 subLeft 一定没有左子树,所以只需要判断 subLeft 在 subParent 的左还是右,再通过 subParent 的左或者右链接 subLeft 的右即可


析构

private:
	void Destroy(Node*& root)
	{
		// 后续递归
		if (root == nullptr)
		{
			return;
		}

		// 左子树 - 右子树 - 根
		Destroy(root->_left);
		Destroy(root->_right);

		delete root;
	}

public:
	~BSTree()
	{
		_~BSTree(_root);
	}

拷贝构造

private:
	Node* copy(Node* root)
	{
		// 前序拷贝
		if (root == nullptr)
		{
			return nullptr;
		}

		// 根 - 左子树 - 右子树
		Node* newRoot = new Node(root->_key);
		newRoot->_left = copy(root->_left);
		newRoot->_right = copy(root->_right);

		return newRoot;
	}

public:
	BSTree(const BSTree<K>& t)
	{
		_root = copy(t._root);
	}

重载赋值运算符

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

	return *this;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值