C++——二叉搜索树

1.二叉搜索树概念

二叉搜索树(Binary Search Tree,BST) 是一种特殊的二叉树,它具有以下性质:

  1. 每个节点有一个值(通常是数字或者可以比较大小的其他数据类型)。
  2. 对于每个节点,左子树中的所有节点值都小于该节点的值,右子树中的所有节点值都大于该节点的值。
  3. 每个节点的左、右子树也都是二叉搜索树。

1.1 二叉搜索树的基本操作:

  1. 插入:从根节点开始,找到合适的位置(比当前节点小的去左子树,比当前节点大的去右子树),直到空节点处插入新节点。
  2. 查找:从根节点开始,若目标值小于当前节点值,继续在左子树查找;若目标值大于当前节点值,继续在右子树查找。
  3. 删除:有三种情况:
    1. 删除的节点是叶子节点(没有子节点):直接删除。
    2. 删除的节点有一个子节点:用该子节点替代被删除的节点。
    3. 删除的节点有两个子节点:用右子树中的最小节点或左子树中的最大节点替代被删除节点。

1.2 性能分析

  1. 时间复杂度(Time Complexity)

    • 最优情况:当树保持平衡时,每个操作的时间复杂度为O(log n),其中n是树中的节点数。
    • 最坏情况:如果树退化成一个链表(例如每次插入的值都比当前节点大或者小),那么操作的时间复杂度为O(n)。
    • 平均情况:对于随机插入的节点,树的平均高度接近log n,因此操作的时间复杂度通常是O(log n)。
  2. 空间复杂度(Space Complexity)

二叉搜索树的空间复杂度主要取决于树的高度。对于n个节点的树,空间复杂度为O(n),因为每个节点都需要存储数据和指向左右子树的指针。

2. 二叉搜索树的实现

二叉搜索树的初步实现:

cpp 复制代码
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(const BSTree<K>& t)
	{
		_root = Copy(t._root);
	}

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

	~BSTree()
	{
		Destory(_root);
		_root == nullptr;
	}


	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

private:
	void _InOrder(Node* root)
	{
		if (root->_key == nullptr)
		{
			return;
		}

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

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

		Destory(root->_left);
		Destory(root->_right);
		delete(root);
	}

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

		Node* copy = new Node(root->_key);
		copy->_left = Copy(root->_left);
		copy->_right = Copy(root->_right);

		return copy;
	}

private:
	Node* _root = nullptr;
};

2.1二叉搜索树的插入

在二叉搜索树中,每个节点都有一个键值。其插入操作遵循特定的规则:

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

过程:

  1. 如果树为空,则直接创建一个新的节点,并将其赋值给根指针 _root

  2. 如果树不为空,按照二叉搜索树的规则,若插入的值大于当前节点的值,则向右子树移动;若插入的值小于当前节点的值,则向左子树移动,直到找到一个空位来插入新节点。

  3. 如果允许插入相等的值,遇到与当前节点值相等的情况时,可以选择将其插入到右子树或左子树。这时,确保插入逻辑的一致性,避免重复值总是被插入到某一侧。

这样,我们保证插入操作能够在符合二叉搜索树规则的同时,保持树的结构合理性。

cpp 复制代码
bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;

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

	cur = new Node(key);

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

	return true;
}

这段代码使用了迭代方式来插入二叉搜索树的节点,避免了递归的深度。在树的查找过程中,迭代地比较当前节点与目标值的大小,决定向左子树还是右子树递归。如果插入位置为空,则创建新的节点并将其连接到父节点。插入操作会检查是否插入重复的节点,如果有相同的键值,则返回 false,避免重复插入,并且插入操作中判断左右子树的顺序不能随意交换。

2.2二叉搜索树的查找

在二叉搜索树中,查找操作是非常高效的,因为树的结构保持了一定的排序规则:左子树所有节点的值小于当前节点的值,右子树所有节点的值大于当前节点的值。

二叉搜索树的查找过程:

  1. 比较根节点与目标值

    • 如果目标值等于当前节点的值,则查找成功。
    • 如果目标值小于当前节点的值,则继续在当前节点的左子树查找。
    • 如果目标值大于当前节点的值,则继续在当前节点的右子树查找。
  2. 时间复杂度

    • 最好的情况是树是完全平衡的,此时查找时间复杂度是 O(log n)
    • 最坏的情况是树退化成链表(例如每次插入的值都比当前节点大或小),此时查找的时间复杂度是 O(n)
cpp 复制代码
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
		{
			cout << "找到了" << endl;
			return true;	
		}
	}

	cout << "没找到了" << endl;
	return false;
}

2.3二叉搜索树的删除

删除过程分析:

  1. 删除叶子节点

    如果节点没有左右子树,直接将其父节点指向空,删除该节点。

  2. 删除只有一个子节点的节点

    将其父节点的指针指向它的唯一子节点,然后删除该节点。

  3. 删除有两个子节点的节点

    需要找到一个替代节点。通常我们选择右子树的最小节点(中序后继)或左子树的最大节点(中序前驱)。然后将该节点的值替换到要删除的节点,并递归删除该替代节点。

cpp 复制代码
bool Erase(const K& key)
{
	Node* parent = nullptr;
	Node* cur = _root;
	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;
				}
			}
			else if (cur->_right == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					if (cur = parent->_left)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}

					delete cur;
				}
			}
			//两个孩子
			else
			{
				Node* pMinRight = cur;
				Node* minRight = cur->_right;

				while (minRight->_left)
				{
					pMinRight = minRight;
					minRight = minRight->_left;
				}

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

				if (pMinRight->_left = minRight)
				{
					pMinRight->_left = minRight->_right;
				}
				else
				{
					pMinRight->_right = minRight->_right;
				}
				delete minRight;
			}

			return true;
		}
	}
	return false;
}

3.key/value搜索

每个关键码(key)都有对应的值(value),其中值(value)可以是任意类型的对象。在树的结构中,每个节点除了存储关键码(key),还需要存储与之对应的值(value)。在进行插入、删除或查找操作时,仍然遵循以关键码(key)为关键字的二叉搜索树规则,可以快速地查找到对应关键码(key)所对应的值(value)。

cpp 复制代码
template<class K, class V>
struct BSTreeNode
{
	BSTreeNode<K, V>* _left;
	BSTreeNode<K, V>* _right;

	K _key;  
	V _value; 

	BSTreeNode(const K& key, const V& value)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
		, _value(value)
	{}
};

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

	BSTree() = default;


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


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

		return *this;
	}

	~BSTree()
	{
		Destory(_root);
		_root = nullptr;
	}

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

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

		cur = new Node(key, value);

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

		return true;
	}

	Node* 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 cur;
			}
		}

		return nullptr;
	}

	bool Erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		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;
				}
				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;
				}
				else
				{
					Node* pMinRight = cur;
					Node* minRight = cur->_right;
					while (minRight->_left)
					{
						pMinRight = minRight;
						minRight = minRight->_left;
					}

					swap(cur->_key, minRight->_key);
					if (pMinRight->_left == minRight)
					{
						pMinRight->_left = minRight->_right;
					}
					else
					{
						pMinRight->_right = minRight->_right;
					}

					delete minRight;
				}

				return true;
			}
		}

		return false;
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

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

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

		Destory(root->_left);
		Destory(root->_right);
		delete root;
	}

	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;

		Node* copy = new Node(root->_key, root->_value);
		copy->_left = Copy(root->_left);
		copy->_right = Copy(root->_right);

		return copy;
	}

private:
	Node* _root = nullptr;
};
相关推荐
澪吟2 小时前
C++ 从入门到进阶:核心知识与学习指南
开发语言·c++
热爱编程的小白白2 小时前
【Playwright自动化】安装和使用
开发语言·python
听风吟丶2 小时前
Java NIO 深度解析:从 BIO 到 NIO 的演进与实战
开发语言·python
学历真的很重要2 小时前
LangChain V1.0 Messages 详细指南
开发语言·后端·语言模型·面试·langchain·职场发展·langgraph
sali-tec2 小时前
C# 基于halcon的视觉工作流-章58-输出点云图
开发语言·人工智能·算法·计算机视觉·c#
lpfasd1232 小时前
Rust + WebAssembly:让嵌入式设备被浏览器调试
开发语言·rust·wasm
_OP_CHEN2 小时前
算法基础篇:(四)基础算法之前缀和
c++·算法·前缀和·蓝桥杯·acm·icpc·算法竞赛
lion King7762 小时前
c++八股:explicit
开发语言·c++
初见无风2 小时前
4.3 Boost 库工具类 optional 的使用
开发语言·c++·boost