二叉搜索树--高效的数据管理

目录

一、二叉搜索树的定义与性质

1.1、什么是二叉搜索树?

1.2、优势

二、二叉搜索树的核心操作

2.1、整体结构的定义

2.2、查找(Find)

2.3、插入(Insert)

2.4、删除(erase)

三、BST局限性:平衡问题

3.1、时间退化问题

3.2、解决方案:平衡二叉搜索树

四、完整代码实现(C++)


引言

如何在大量数据中进行高效的搜索、插入和删除呢?就比如:

想象一下,你正在图书馆的书架上寻找一本特定编号的书籍。如果书籍杂乱无章,你可能需要逐本翻找;但如果它们按编号从小到大排列,你可以快速定位目标。二叉搜索树(Binary Search Tree, BST) 正是实现这种“高效查找”的核心数据结构之一。本文将深入解析一下其特点、工作原理及操作实现。


一、二叉搜索树的定义与性质

1.1、什么是二叉搜索树?

二叉搜索树(BST)是一颗二叉树,是重要的树形结构之一。其性质,简单来说就是:

  • 对任意节点,其左子树的所有节点值均小于该节点值右子树的所有节点值均大于该节点值。
  • 左子树和右子树本身也是二叉搜索树。

结构如下:

1.2、优势

  • 高效:时间复杂度跟搜索树的高度有关,跟节点个数无关,插入、删除、查找操作的平均时间复杂度为 O(log n)

  • 有序遍历:中序遍历(左-根-右)可以得到一个升序序列。


二、二叉搜索树的核心操作

2.1、整体结构的定义


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:
	// ...其他操作

private:
	Node* _root; //指向二叉搜索树的根节点
};

2.2、查找(Find)

逻辑:从根节点开始,逐层比较目标值与当前节点:

  • 若目标值等于当前节点值,返回节点。

  • 若目标值小于当前节点值,递归查找左子树。

  • 若目标值大于当前节点值,递归查找右子树。

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

	return false;
}

2.3、插入(Insert)

逻辑:跟查找差不多,查找到合适位置就插入新节点。如果树里面有该值就不插入了。

步骤

  1. 如果树为空,创建新节点作为树节点。
  2. 不为空就找位置,插入值比当前节点值小就去左树找,否则就去右树找。
bool Insert(const K& key) // 插入一个键值为key的节点
{
	if (_root == nullptr) // 如果树为空
	{
		_root = new Node(key); // 创建一个新的根节点
		return true; // 插入成功
	}

	Node* parent = nullptr; // 用于记录当前节点的父节点
	Node* cur = _root; // 从根节点开始查找插入位置
	while (cur) // 遍历树,直到找到合适的插入位置
	{
		if (key < cur->_key) // 如果待插入的键值小于当前节点的键值
		{
			parent = cur; // 更新父节点为当前节点
			cur = cur->_left; // 向左子树移动
		}
		else if (key > cur->_key) // 如果待插入的键值大于当前节点的键值
		{
			parent = cur; // 更新父节点为当前节点
			cur = cur->_right; // 向右子树移动
		}
		else // 如果待插入的键值等于当前节点的键值
		{
			return false; // 表示树中已存在该键值,插入失败
		}
	}

	cur = new Node(key); // 创建一个新的节点
	if (key < parent->_key) // 根据键值大小关系,将新节点插入到父节点的左子树
	{
		parent->_left = cur;
	}
	else // 或者插入到父节点的右子树
	{
		parent->_right = cur;  
	}

	return true; // 插入成功
}

2.4、删除(erase)

逻辑:查找到要删除的节点时,删除操作分三种情况处理,确保删除后仍是一颗搜索二叉树。
情况一:当删除的是叶子节点。

直接删除即可

情况二:删除仅有一个子节点的节点。

用子节点替代被删除节点。

情况三:删除有两个子节点的节点。

找替代节点。找左子树的最大节点(或右子树的最小节点)替代被删除的节点,然后删除替代节点即可。

代码示例:

bool erase(const K& key) // 删除键值为key的节点
{
	if (_root == nullptr) 
	{
		return false; 
	}

	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (key < cur->_key) 
		{
			parent = cur;
			cur = cur->_left; 
		}
		else if (key > cur->_key)
		{
			parent = cur; 
			cur = cur->_right;
		}
		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;
					}
				}
			}
			// 处理待删除节点右子树为空的情况
			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;
					}
				}
			}
			// 处理待删除节点左右子树都不为空的情况
			else
			{
				// 找替代节点,这里选择左子树中最大的节点作为替代节点
				Node* parent = cur; // 重新定义一个变量parent,用于记录替代节点的父节点
				Node* leftMax = cur->_left; // 从待删除节点的左子节点开始查找左子树中最大的节点
				while (leftMax->_right) // 找到左子树中最大的节点
				{
					parent = leftMax;
					leftMax = leftMax->_right;
				}

				// 将替代节点的键值与待删除节点的键值交换
				swap(leftMax->_key, cur->_key);

				// 删除替代节点
				if (parent->_left == leftMax)
				{
					parent->_left = leftMax->_left;
				}
				else
				{
					parent->_right = leftMax->_left;
				}

				cur = leftMax; // 将cur指向替代节点
			}
			delete cur; // 删除待删除节点或替代节点
			return true; // 删除成功
		}
	}

	return false; // 如果遍历完树都没有找到待删除节点,返回false
}

三、BST局限性:平衡问题

3.1、时间退化问题

当数据按顺序插入如(5,4,3,2,1),BST会退化成链表,时间复杂度会退化成O(n).

3.2、解决方案:平衡二叉搜索树

因为访问二叉搜索树的效率跟树的高度有关,所以在不破坏搜索树结构的同时,通过自动调整树结构保持低高度:


四、完整代码实现(C++)


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()
		:_root(nullptr)
	{}

	BSTree(BSTree<K>& t)
	{
		_root = Copy(t._root);
	}

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

	~BSTree()
	{
		Destroy(_root);
	}

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

		return false;
	}

	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

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

		return true;
	}

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

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

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

	bool erase(const K& key)
	{
		if (_root == nullptr)
		{
			return false;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			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;
						}
					}
				}
				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;
						}
					}
				}
				else//左右都不为空时
				{
					//找替代节点
					//找左子树最大的节点或找右子树最小的节点
					Node* parent = cur;
					Node* leftMax = cur->_left;
					while (leftMax->_right)
					{
						parent = leftMax;
						leftMax = leftMax->_right;
					}

					swap(leftMax->_key, cur->_key);

					if (parent->_left == leftMax)
					{
						parent->_left = leftMax->_left;
					}
					else
					{
						parent->_right = leftMax->_left;
					}

					cur = leftMax;
				}
				delete cur;
				return true;
			}
		}

		return false;
	}

    ///
    //递归版
	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);
	}

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

		Node* copyroot = new Node(root->_key);
		copyroot->_left = Copy(root->_left);
		copyroot->_right = Copy(root->_right);
		return copyroot;
	}

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

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

	bool _eraseR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}

		if (root->_key > key)
		{
			return _eraseR(root->_left, key);
		}
		else if (root->_key < key)
		{
			return _eraseR(root->_right, key);
		}
		else
		{
			Node* del = root;

			//1、左为空
			//2、右为空
			//3、左右都不为空
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else
			{
				//找替代点
				Node* leftMax = root->_left;
				while (leftMax->_right)
				{
					leftMax = leftMax->_right;
				}

				swap(leftMax->_key, root->_key);

				return _eraseR(root->_left, key);
			}

			delete del;
			return true;
		}
	}

	bool _InsertR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}

		if (root->_key > key)
		{
			return _InsertR(root->_left, key);
		}
		else if (root->_key < key)
		{
			return _InsertR(root->_right, key);
		}
		else
		{
			return false;
		}
	}

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

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


private:
	Node* _root;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值