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的位置:

相关推荐
会编程的土豆2 小时前
【数据结构与算法】新二叉树
数据结构·算法·二叉树
Morwit2 小时前
【力扣hot100】 416. 分割等和子集
数据结构·c++·算法·leetcode·职场和发展
qeen872 小时前
【算法笔记】二分查找与二分答案
c语言·c++·笔记·学习·算法·二分
C雨后彩虹2 小时前
投篮大赛问题
java·数据结构·算法·华为·面试
Sylvia-girl2 小时前
类与对象(下)
c++·友元函数·类与对象
Hello eveybody2 小时前
介绍最大公因数和最小公约数(C++)
java·开发语言·c++
宵时待雨2 小时前
优选算法专题3:二分查找
数据结构·c++·算法·leetcode·职场和发展
Byte不洛2 小时前
理解C++异常机制:栈展开、异常传播与异常安全
c++·异常处理·后端开发·编程基础·try catch
我头发多我先学2 小时前
C++ AVL 树:平衡原理到完整实现(自平衡二叉搜索树)
开发语言·数据结构·c++·算法