c++数据结构之二叉搜索树

一.概念

二叉搜索树是一种特殊的二叉树,左子树上所有节点的值,一定小于等于根节点,右子树上所有节点的值,一定大于等于根节点。

二.关于复杂度的探讨

下面内容需要有关二叉树复杂度的知识储备,如有需要,请看这篇文章。

https://blog.csdn.net/2401_87941709/article/details/155384131?fromshare=blogdetail&sharetype=blogdetail&sharerId=155384131&sharerefer=PC&sharesource=2401_87941709&sharefrom=from_linkhttps://blog.csdn.net/2401_87941709/article/details/155384131?fromshare=blogdetail&sharetype=blogdetail&sharerId=155384131&sharerefer=PC&sharesource=2401_87941709&sharefrom=from_link

以一颗普通二叉搜索树来说,假设一个极端情况,去找树里有没有5,那么此时就会比对h次,我们通过等比数列求和知道,h = log(N),所以按理来说最坏复杂度应该是log(N)

但这仅仅是一颗普通二叉树的最坏情况,如果二叉树本身结构就是一种非常极端的情况呢?

这时候还是去找5,依旧比对h次,但此时h与n直接近似,那么复杂度就退化成**O(N)**了,也就是说效率大大降低,二叉树直接退化到跟list,vector坐一桌了;更可气的是二叉树的实现比list和vector更为复杂,那么遇到这种情况,还不如用容器两兄弟来的实在。所以该如何解决这种效率低下的极端结构下的极端情况呢?

答案是平衡二叉搜索树和哈希,在后面的文章会讲到。

三.实现搜索二叉树

注:搜索章节里,模板参数不再叫作T,而是叫做K(key,关键字的意思),来代表关键字的类型

1.Insert

Ⅰ.相同值是否可插入的探讨

Ⅱ.不允许冗余(相同)插入的情况

遍历的过程既可用循环,也可用递归。但递归过深容易栈溢出,所以这里用循环。

要插入一个节点,首先比较它与当下父节点的大小,如果待插入节点大于父节点,往右子树走,如果小于,往左子树走。

复制代码
template<class K>
class BSTree
{
	typedef BSTNode<K> Node;
public:
	bool Insert(const K& key)
	{
		//判空
		if (_root == nullptr)
		{
			_root = new(Node);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{//让cur去遍历,每遍历一次之前就把cur的值赋值给parent,然后自己接着向下遍历。反复操作,直至cur为空。此时就可以在parent处插入子节点了。
			if (key > cur->_key)
			{//待插入值大,往右子树走
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{//待插入值小,往左子树走
				parent = cur;
				cur = cur->_left;
			}
			else//不允许存在值相同的节点
			{
				return false;
			}
		}
		Node* newnode = new Node(key);
		if (key > parent->_key)
		{
			parent->_right = newnode;
		}
		else if (key < parent->_key)
		{
			parent->_left = newnode;
		}
		return true;
	}
private:
	Node* _root = nullptr;
};

2.中序遍历

代码写好之后,_root和函数_Inorder()都是私有的,在使用上就很不方便,此时就有个小巧思,再在public区域封装个套壳函数:Inorder(),毕竟public区域里成员函数既可以去使用private区域的成员,也可以在类外被调用。

有个有趣的现象,二叉搜索树走中序遍历后的序列是顺序的。

复制代码
	void Inorder()
	{//套壳函数,方便访问private的成员。
		_Inorder(_root);
	}
private:
	void _Inorder(Node* root)//中序遍历
	{
		if (root == nullptr)
		{
			return;
		}
		_Inorder(root->_left);
		cout << root->_key << " ";
		_Inorder(root->_right);
	}
	Node* _root = nullptr;

3.查找

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

上面三位都是弟弟,接下来好戏才开场。

4.正式拷打------删除

由易到难分为三种情况:

假设待删除的节点为N。

①.N是叶子节点,直接释放,并让N父节点指向N的指针指向空。

②.N只有一颗子树(无论左右),改变N父节点对应子节点指针,使其指向N的子节点。

③.N左右两颗子树都存在,此时若想效仿②中操作是行不通的,二叉树一个父节点只能有两个子节点,此时若N父节点除N外,还有一颗子树,那么将N的两颗子树接给N父节点后,它就有了三颗子树,是不符合二叉树定义的。

解决方案:替换法。假设替换节点为R。R为N左子树的最右节点(左子树里最大的节点),或是N右子树的最左节点(右子树最小)中任意一个,这能保证替换,删除完以后整颗树还是二叉搜索树。

再来说说替换,将N与R节点的值交换,然后再释放掉R节点,并让R的父节点指向R节点的指针指向空。

图例:

删除的代码实现

遍历

先让cur在树里遍历(cur初始为_root,cur->_key与key比对,key小于_key,cur向左;key大于_key,向右),把每次向下遍历前的cur赋值给parent,然后cur向下遍历,当cur指向节点的值与key相等,就找到了要删除的目标。

删除

①.待删除节点cur只有一颗子树(将待删除的叶子节点也划归于此)

先判断cur的子树哪边是空。

再判断cur究竟是parent的左子节点还是右子节点。

然后将指向parent子节点的指针指向cur的子节点。

删除根的极端情况:

此时需要改变_root的指向,让其指向值为3的节点(根节点变成3)

②.cur有两颗子树

先确定替换节点replace,此处用cur右子树最左节点

找到replace向下找到合适位置后,替换加删除。

复制代码
else
{
				//找替换节点,这里找cur右子树的最左节点。
				Node* replace = cur->_right;
				Node* replaceParent = nullptr;//须有替换节点的父节点,不然后面删除操作搞不了。
				while (replace->_left)
				{
					replaceParent = replace;
					replace = replace->_left;
				}
				//替换
				cur->_key = replace->_key;
				//删除
				replaceParent->_left = replace->_right;
				delete replace;
}
问题分析

but...man,what can i say?删除根节点8还是走不通,还空指针引用了????

来结合图和代码逻辑分析一下:

由于8这个根节点不满足两个向下遍历的if条件,那么cur = 根节点,进入删除分支以后,由于根节点右子树最左节点**(replace->_left)为空** ,那么replace和replaceparent不进while循环,那么replaceparent无法更新,还是空指针。 那么执行删除操作时,replaceparent->_left就是引用空指针。

解决方案

将replaceparent的初始值设为cur,而非nullptr。

同时还有一个问题:

并非所有replace都是replaceparent的左子节点。

需要去判断一下replace的位置:

相关推荐
老当益壮梁奶奶11 小时前
Linux软件编程学习笔记(七):线程分离与线程间通信详解
linux·c语言·c++·笔记·学习
船厂电气自动化ai大模型14 小时前
AI大模型与数学·第57课 傅里叶全套工具链综合实战:串联级数/连续变换/DFT/FFT,图像、音频、扩散模型完整例题
数据结构·人工智能·深度学习·算法·机器学习
wuyk55514 小时前
从零吃透Modbus通信|第3章:STM32 Modbus RTU主机
服务器·开发语言·网络·数据结构·stm32·单片机·嵌入式硬件
Brilliantwxx15 小时前
【算法从零到千】【59-65】链表专题
数据结构·链表
重生之后端学习16 小时前
53. 最大子数组和[中等]✅
java·数据结构·算法·leetcode·职场和发展
水饺编程16 小时前
第5章,[Win32 章节] :绘图模式
c语言·c++·windows·visual studio
INGNIGHT16 小时前
1908 · 布尔表达式求值(stack单调栈&dfs)
开发语言·c++·算法
小小杨树17 小时前
【C++】学习:双缓冲视频流水线深解析
c++·性能优化·音视频开发
青少儿编程课堂18 小时前
图形化编程实战:智能交通灯调度台,一个作品讲透循环、条件与广播
c++·python·算法·bfs·信息学竞赛
赵民勇18 小时前
C++无锁编程详解
c++