搜索二叉树

一、二叉搜索树的概念

⼆叉搜索树⼜称⼆叉排序树,它可以是⼀棵空树,或者是具有以下性质的⼆叉树:

  • 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值
  • 若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值
  • 它的左右⼦树也分别为⼆叉搜索树

如下:

⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义。

二、二叉搜索树性能

最优情况下,⼆叉搜索树为完全⼆叉树(或者接近完全⼆叉树),其⾼度为:O(logN)

最差情况下,⼆叉搜索树退化为单⽀树(类似链表),其⾼度为:O(N)

所以综合⽽⾔⼆叉搜索树增删查改时间复杂度为:O(N)

如果要追求较高的查找、插入和删除效率,通常会选择使用平衡二叉搜索树(如AVL树、红黑树等),这些树通过特定的旋转操作来保持树的平衡,从而确保操作的时间复杂度保持在O(log n)。

另外需要说明的是,⼆分查找也可以实现O(logN) 级别的查找效率,但是⼆分查找有两⼤缺陷:

  1. 需要存储在⽀持下标随机访问的结构中,并且有序。

  2. 插⼊和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数据。

这⾥也就体现出了平衡⼆叉搜索树的价值。

三、二叉搜索树的增删查

1.插入

1.树为空树:直接新增节点赋值给root

2.树不为空:按⼆叉搜索树性质,插⼊值⽐当前结点⼤往右⾛,插⼊值⽐当前结点⼩往左⾛,找到空位置,插⼊新结点。

  1. 如果⽀持插⼊相等的值,插⼊值跟当前结点相等的值可以往右⾛,也可以往左⾛,找到空位置,插⼊新结点。(要注意的是要保持逻辑⼀致性,插⼊相等的值不要⼀会往右⾛,⼀会往左⾛。)

如下:

cpp 复制代码
bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
	Node* ky = new Node(key);
	Node* cur = _root, *parent = nullptr;
	while (cur)
	{
		parent = cur;
		if (key < cur->_key) cur = cur->left;
		else if (key > cur->_key) cur = cur->right;
		else return false;
	}
	if (key < parent->_key) parent->left = ky;
	else parent->right = ky;
	return true;
}

2.查找

  1. 从根开始⽐较,查找key,key⽐根的值⼤则往右边⾛查找,key⽐根值⼩则往左边⾛查找。

  2. 最多查找⾼度次,⾛到空还没找到,这个值不存在。

  3. 如果不⽀持插⼊相等的值,找到key即可返回。

  4. 如果⽀持插⼊相等的值,意味着有多个key存在,⼀般要求查找中序的第⼀个key。

查找与插入的逻辑类似如下:

cpp 复制代码
Node* 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 cur;
	}
	return nullptr;
}

3.删除

对于二叉搜索树的删除操作是比较麻烦的因为不仅要把对应的节点删除还要保持它原有的性质不变,那么比如要删除key我们来分两种情况来讨论:

1.key的还子小于两个(即叶子节点或单枝)

对于该情况可以直接让key的父节点指向它的孩子,然后删除key就行。因为这种情况只有一个孩子或没有孩子比较好管理。

2.key有两个孩子

因为key有两个孩子,所以把key删除后如何管理它的孩子是个问题,很容易把整颗树的结构和性质改变。这里的解决方法是不直接把key对应的这块空间删除,而是找到key左树上的最大值或者右树上的最大值(记为cur)。然后把cur的值与key的值做交换(或者不用交换,把cur的值存储在key里面),然后删除cur这块空间。

cpp 复制代码
bool Erase(const K& key)
{
	Node* det = _root, parent = _root;
	while (det)
	{
		parent = det;
		if (key < det->_key) det= det->left;
		else if (key > det->_key) det = det->right;
		else break;
	}
	if (det == nullptr) return true;
	if (det->left == nullptr || det->right == nullptr)
	{
		if (det->left == nullptr)
		{
			if (parent->left == det) parent->left = det->right;
			else parent->right = det->right;
		}
		else
		{
			if (parent->left == det) parent->left = det->left;
			else parent->right = det->left;
		}
		delete det;
	}
	else
	{
		Node* cur = det->left;
		while (cur->right)
		{
			cur = cur->right;
		}
		det->_key = cur->_key;
		delete cur;
		cur = nullptr;
	}

四、key和key/valued

二叉搜索树里面除了储存key以外,还可以同时储存key和valued,每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字⾛⼆叉搜索树的规则进⾏⽐较,可以快速查找到key对应的value。key/value的搜索场景实现的⼆叉树搜索树⽀持修改,但是不⽀持修改key,修改key破坏搜索树结构了,可以修改value。

五、源码

cpp 复制代码
template<class K, class V>
class BSTreeNode
{
public:
	K _key;
	V _val;
	BSTreeNode* left;
	BSTreeNode* right;
	BSTreeNode(K key,V val)
		:_key(key)
		,_val(val)
		,left(nullptr)
		,right(nullptr){}
};
template<class K, class V>
class BSTree
{
	typedef BSTreeNode<K, V> Node;
public:
	bool Insert(const K& key, const V& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(key, val);
			return true;
		}
		Node* ky = new Node(key, val);
		Node* cur = _root, *parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (key < cur->_key) cur = cur->left;
			else if (key > cur->_key) cur = cur->right;
			else return false;
		}
		if (key < parent->_key) parent->left = ky;
		else parent->right = ky;
		return true;
	}
	Node* 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 cur;
		}
		return nullptr;
	}
	bool Erase(const K& key)
	{
		Node* det = _root, parent = _root;
		while (det)
		{
			parent = det;
			if (key < det->_key) det= det->left;
			else if (key > det->_key) det = det->right;
			else break;
		}
		if (det == nullptr) return true;
		if (det->left == nullptr || det->right == nullptr)
		{
			if (det->left == nullptr)
			{
				if (parent->left == det) parent->left = det->right;
				else parent->right = det->right;
			}
			else
			{
				if (parent->left == det) parent->left = det->left;
				else parent->right = det->left;
			}
			delete det;
		}
		else
		{
			Node* cur = det->left;
			while (cur->right)
			{
				cur = cur->right;
			}
			det->_key = cur->_key;
			det->_val = cur->_val;
			delete cur;
			cur = nullptr;
		}
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr) return;
		_InOrder(root->left);
		cout << root->_key << ":" << root->_val;
		_InOrder(root->right);
	}
	void InOrder()
	{
		_InOrder(_root);
	}
	~BSTree()
	{
		Free(_root);
		_root = nullptr;
	}
	void Free(Node* root)
	{
		if (root == nullptr) return;
		Free(root->left);
		Free(root->right);
		delete root;
	}
private:
	Node* _root = nullptr;
};
相关推荐
爱吃生蚝的于勒2 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
ChoSeitaku7 小时前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
Fuxiao___7 小时前
不使用递归的决策树生成算法
算法
我爱工作&工作love我7 小时前
1435:【例题3】曲线 一本通 代替三分
c++·算法
白-胖-子8 小时前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-统计数字
开发语言·c++·算法·蓝桥杯·等考·13级
workflower8 小时前
数据结构练习题和答案
数据结构·算法·链表·线性回归
好睡凯8 小时前
c++写一个死锁并且自己解锁
开发语言·c++·算法
Sunyanhui18 小时前
力扣 二叉树的直径-543
算法·leetcode·职场和发展
一个不喜欢and不会代码的码农8 小时前
力扣105:从先序和中序序列构造二叉树
数据结构·算法·leetcode
前端郭德纲8 小时前
浏览器是加载ES6模块的?
javascript·算法