当前位置: 首页 > news >正文

C++ --- 二叉搜索树

1 二叉搜索树的概念

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

1 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值

2 若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值

3 它的左右⼦树也分别为⼆叉搜索树

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

2 二叉搜索树的性能分析 

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

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

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

那么这样的效率显然是⽆法满⾜我们需求的,我们后续课程需要继续讲解⼆叉搜索树的变形,平衡⼆叉搜索树AVL树和红⿊树,才能适⽤于我们在内存中存储和搜索数据。

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

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

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

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

 3 二叉搜索树的插入

 插⼊的具体过程如下:

1. 树为空,则直接新增结点,赋值给root指针

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

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

        代码实现:

bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		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);
	if (key > parent->_key)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}

	return true;
}

4 二叉搜索树的查找 

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

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

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

4. 如果⽀持插⼊相等的值,意味着有多个x存在,⼀般要求查找中序的第⼀个x。如下图,查找3,要 找到1的右孩⼦的那个3返回

        代码实现:

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

5 二叉搜索树的删除 

 ⾸先查找元素是否在⼆叉搜索树中,如果不存在,则返回false。

如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)

1. 要删除结点N左右孩⼦均为空

2. 要删除的结点N左孩⼦位空,右孩⼦结点不为空

3. 要删除的结点N右孩⼦位空,左孩⼦结点不为空

4. 要删除的结点N左右孩⼦结点均不为空

对应以上四种情况的解决⽅案:

1. 把N结点的⽗亲对应孩⼦指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是⼀样 的)

2. 把N结点的⽗亲对应孩⼦指针指向N的右孩⼦,直接删除N结点

3. 把N结点的⽗亲对应孩⼦指针指向N的左孩⼦,直接删除N结点

4. ⽆法直接删除N结点,因为N的两个孩⼦⽆处安放,只能⽤替换法删除。找N左⼦树的值最⼤结点 R(最右结点)或者N右⼦树的值最⼩结点R(最左结点)替代N,因为这两个结点中任意⼀个,放到N的 位置,都满⾜⼆叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转⽽变成删除R结 点,R结点符合情况2或情况3,可以直接删除。

代码实现:

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

				delete cur;
			}
			else if (cur->_right == nullptr)
			{
				if (cur == _root)
				{
					_root = _root->_left;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}

				delete cur;
			}
			else
			{
				Node* replaceParent = cur;
				Node* replace = cur->_right;

				while (replace->_left)
				{
					replaceParent = replace;
					replace = replace->_left;
				}

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

				if (replaceParent->_left == replace)
					replaceParent->_left = replace->_right;
				else
					replaceParent->_right = replace->_right;

				delete replace;
			}
			return true;
		}
	}
	return false;
}

 二叉树的遍历

那么上面写完了我们总得是要测试一下的,那么为了输出的结果好看,二叉搜索树的中序遍历就可以将输出的结果按照升序的样式打印出来。

        代码实现: 

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

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

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

在这里是因为,在外部调用InOrder的话,root并不作为public对象对外可以进行访问的,因此单纯的写一个InOrder函数达不到打印的效果,但是可以在创建一个无参的InOrder来进行调用,这样内部的函数进行调用就不会出现那种情况了,那么就可以达到我们想要的效果了。

 到这里关于二叉搜索树的介绍就到这里了,感谢你的观看!

相关文章:

  • Ubuntu 重置密码方法
  • 【教学类-58-14】黑白三角拼图12——单页1页图。参考图1页6张(黑白、彩色)、板式(无圆点、黑圆点、白圆点)、宫格2-10、张数6张,适合集体操作)
  • 使用飞书API自动化更新共享表格数据
  • C++ 继承:面向对象编程的核心概念(一)
  • STL之list
  • Selenium之Web Driver常用属性
  • WPF ContentPresenter详解2
  • Docker-清理容器空间prune
  • 顺序表(C语言源码详解,附加测试代码)
  • 波斯白板(Persian Whiteboard)
  • MFC中如何判断一个窗口当前状态是显示还是隐藏
  • Python 异常处理完全指南
  • 基于YOLOv8深度学习的PCB缺陷检测识别系统【python源码+GUI界面+数据集+训练代码+登录界面】
  • [AI绘图] ComfyUI 中自定义节点插件安装方法
  • 雕琢健康生活,奏响养生乐章
  • 使用Geotools从DEM数据中读取指定位置的高程实战
  • 【图论】网络流算法入门
  • 初阶7 vector
  • Mybatis错误resultMap must match
  • [思路提供]Mysql主从复制时的网络延迟很高,如何调整MySQL复制参数
  • 包揽金银!王宗源、郑九源夺得跳水世界杯总决赛男子3米板冠亚军
  • 抢抓消费旺季:五一假期,多地党政主官调研外贸优品展销活动
  • 科普|“小石头,大麻烦”,出现输尿管结石如何应对?
  • 美国证实加拿大及墨西哥汽车零部件免关税
  • 专家分析丨乌美签署矿产协议,展现美外交困境下的无奈
  • 首部关于民营经济发展的基础性法律,有何亮点?专家解读