C++——二叉搜索树
创始人
2024-12-26 18:35:49
0

前言:这篇文章我们将分享一种新的数据结构类型——二叉搜索树。


目录

一、什么是二叉搜索树

二、定义

三、操作

1.插入

 2.遍历 

3.寻找

4.删除

四.完整代码

 总结


一、什么是二叉搜索树

二叉搜索树又称二叉排序树它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

下面我们就来看看二叉搜索树的结构,及其提供的各种操作。 

之所以能被称为二叉排序树,是因为其中序遍历的结果是树中所有节点值的升序序列。 


二、定义

template struct BSTreeNode { 	K _val; 	struct BSTreeNode* _left; 	struct BSTreeNode* _right;  	BSTreeNode(const K& key) 		:_left(nullptr) 		,_right(nullptr) 		,_key(key) 	{} };  template class BSTree { 	typedef BSTreeNode Node; public:  private: 	Node* _root = nullptr; };

整体的定义结构与C语言定义的二叉树一致,只是这里使用的是C++类定义。 


三、操作

1.插入

插入,我们首先要考虑的就是空树的判断

其次要注意,由于二叉搜索树是一个有顺序的树,所以我们就需要按照二叉搜索树的规则与每个节点的值进行比较不断往下走至叶子结点,最后成为新的叶子结点

	bool Insert(const K& val) 	{ 		if (_root == nullptr) 		{ 			_root = new Node(val); 			return true; 		} 		Node* parent = nullptr; 		Node* cur = _root; 		while (cur) 		{ 			if (cur->_val < val) 			{ 				parent = cur; 				cur = cur->_right; 			} 			else if (cur->_val > val) 			{ 				parent = cur; 				cur = cur->_left; 			} 			else 			{ 				return false; 			} 		} 		cur = new Node(val); 		if (val < parent->_val) 		{ 			parent->_left = cur; 		} 		else 		{ 			parent->_right = cur; 		} 		return true; 	}

在定义cur遍历比较的同时,我们还需要定义一个parent来记录最后新节点要插入的父节点。 

由于二叉搜索树要起到搜索的作用,所以不允许存在元素相同的节点,所以我们要规避插入已有元素的情况。 


 2.遍历 

遍历二叉树搜索树,直接采用中序遍历的方法:

	void inOrder(const Node* root) 	{ 		if (root == nullptr) 		{ 			return; 		} 		inOrder(root->_left); 		cout << root->_val << " "; 		inOrder(root->_right); 	}

这里有一个小问题,如果该函数定义在public下,由于要传参数root,而root又是private私有成员,所以我们无法在类外调用私有成员进行参数的传递,所以优化一下:

	void InOrder() 	{ 		inOrder(_root); 	} 	void inOrder(const Node* root) 	{ 		if (root == nullptr) 		{ 			return; 		} 		inOrder(root->_left); 		cout << root->_val << " "; 		inOrder(root->_right); 	}

重新定义一个public成员函数InOrder,并将用该函数作为桥梁去调用inOrder,就可以避免传参

测试如下:


3.寻找

寻找其实就和插入类似,按照大小比较去遍历寻找:

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

4.删除

删除这个操作,可以说是二叉搜索树底层最复杂的一个操作,下面我们就通过下图来进行分析:

关于节点的删除,我们要分析四种情况: 

1.如果说我们要删除1、4、7、13这样的叶节点,那就非常方便,只用找到它,再记录它的父节点,将它删除后,再让父节点原本指向它的指针指向nullptr即可

2.如果是10、14这样的只有一个子节点的节点,我们只需让它的父节点去指向它的子节点即可唯一要注意的是你是父节点的哪边的子节点,就让你的子节点成为哪边的子节点

3.如果是3、6这样的用有两个字节点的节点如果把它删除,无法让它的父节点同时指向两个子节点,所以就必须找到另一个节点来顶替它的位置,否则就破坏了二叉搜索树的规则

那么这个顶替的节点应该是谁呢,要知道,这个新节点必须要比左子节点大,比右子节点小,那么我们就只有两种选择,一个是左子树的最大节点,一个是右子树的最小节点

这两个节点选择哪一个都可以,这里我们以选择右子树的最小节点为例

 下面我们来一步步分析(代码不全仅供参考):

首先我们仍然是要通过循环遍历去找到要删除的节点,找不到则返回false

		Node* parent = nullptr; 		Node* cur = _root; 		while (cur) 		{ 			if (cur->_val < val) 			{ 				parent = cur; 				cur = cur->_right; 			} 			else if (cur->_val > val) 			{ 				parent = cur; 				cur = cur->_left; 			}

其中叶节点的删除是可以和单子节点的删除混合的,来看:

//左为空,父节点指向我的右节点 				if (cur->_left == nullptr) 				{ 					if (cur == _root) 					{ 						_root = cur->_right; 					} 					else 					{ 						if (parent->_left == cur) 						{ 							parent->_left = cur->_right; 						} 						else 						{ 							parent->_right = cur->_right; 						} 					} 					delete cur; 				} 				//右为空,父节点指向我的左节点 				else if(cur->_right == nullptr) 				{ 					if (cur == _root) 					{ 						_root = cur->_left; 					} 					else 					{ 						if (parent->_left == cur) 						{ 							parent->_left = cur->_left; 						} 						else 						{ 							parent->_right = cur->_left; 						} 					} 					delete cur;

因为叶节点的左右子节点都为空,所以会默认进入if条件进行删除。 

这里也比较简单,让cur的父节点原本指向它的指针转而去指向它的子节点

值得注意的是如果要删除的是根节点, 那么就直接让根的单子节点成为新根即可

最后来看拥有双子节点的cur如何删除

                    //左右都不为空,替换法删除 					//寻找右子树的最左节点删除 					Node* rightMinParent = cur; 					Node* rightMin = cur->_right; 					while (rightMin->_left) 					{ 						rightMinParent = rightMin; 						rightMin = rightMin->_left; 					} 					swap(cur->_val, rightMin->_val); 					if (rightMinParent->_left == rightMin) 					{ 						rightMinParent->_left = rightMin->_right; 					} 					else 					{ 						rightMinParent->_right = rightMin->_right; 					} 					delete rightMin;

思路为:找到cur的右子树的最小节点,将两者的数值交换,再去删除该最小节点。 

首先我们需要定义cur的右子树的最小节点,及其父节点,然后去循环遍历找到二者,交换之后,再按照删除叶节点的方式删除该最小节点即可。

测试如下:

删除后我们的顺序并没有变化,说明我们的代码完全正确。

四.完整代码

template struct BSTreeNode { 	K _val; 	struct BSTreeNode* _left; 	struct BSTreeNode* _right;  	BSTreeNode(const K& val) 		:_left(nullptr) 		,_right(nullptr) 		,_val(val) 	{} };  template class BSTree { 	typedef BSTreeNode Node; public: 	//插入 	bool Insert(const K& val) 	{ 		if (_root == nullptr) 		{ 			_root = new Node(val); 			return true; 		} 		Node* parent = nullptr; 		Node* cur = _root; 		while (cur) 		{ 			if (cur->_val < val) 			{ 				parent = cur; 				cur = cur->_right; 			} 			else if (cur->_val > val) 			{ 				parent = cur; 				cur = cur->_left; 			} 			else 			{ 				return false; 			} 		} 		cur = new Node(val); 		if (val < parent->_val) 		{ 			parent->_left = cur; 		} 		else 		{ 			parent->_right = cur; 		} 		return true; 	} 	//寻找 	bool Find(const K& val) 	{ 		if (_root == nullptr) 		{ 			return false; 		} 		Node* cur = _root; 		while (cur) 		{ 			if (cur->_val < val) 			{ 				cur = cur->_right; 			} 			else if (cur->_val > val) 			{ 				cur = cur->_left; 			} 			else 			{ 				return true; 			} 		} 		return false; 	} 	//删除 	bool erase(const K& val) 	{ 		Node* parent = nullptr; 		Node* cur = _root; 		while (cur) 		{ 			if (cur->_val < val) 			{ 				parent = cur; 				cur = cur->_right; 			} 			else if (cur->_val > val) 			{ 				parent = cur; 				cur = cur->_left; 			} 			else 			{ 				//左为空,父节点指向我的右节点 				if (cur->_left == nullptr) 				{ 					if (cur == _root) 					{ 						_root = cur->_right; 					} 					else 					{ 						if (parent->_left == cur) 						{ 							parent->_left = cur->_right; 						} 						else 						{ 							parent->_right = cur->_right; 						} 					} 					delete cur; 				} 				//右为空,父节点指向我的左节点 				else if(cur->_right == nullptr) 				{ 					if (cur == _root) 					{ 						_root = cur->_left; 					} 					else 					{ 						if (parent->_left == cur) 						{ 							parent->_left = cur->_left; 						} 						else 						{ 							parent->_right = cur->_left; 						} 					} 					delete cur; 				} 				else 				{ 					//左右都不为空,替换法删除 					//寻找右子树的最左节点删除 					Node* rightMinParent = cur; 					Node* rightMin = cur->_right; 					while (rightMin->_left) 					{ 						rightMinParent = rightMin; 						rightMin = rightMin->_left; 					} 					swap(cur->_val, rightMin->_val); 					if (rightMinParent->_left == rightMin) 					{ 						rightMinParent->_left = rightMin->_right; 					} 					else 					{ 						rightMinParent->_right = rightMin->_right; 					} 					delete rightMin; 				}  			} 		} 		return false; 	} 	//遍历 	void InOrder() 	{ 		inOrder(_root); 		cout << endl; 	} 	void inOrder(const Node* root) 	{ 		if (root == nullptr) 		{ 			return; 		} 		inOrder(root->_left); 		cout << root->_val << " "; 		inOrder(root->_right); 	} private:  	Node* _root = nullptr; };

 总结

关于二叉搜索树的简单知识就分享到这里,喜欢本篇文章记得一键三连,我们下期再见。

相关内容

热门资讯

七分钟了解!(好彩娱乐)外挂辅... 七分钟了解!(好彩娱乐)外挂辅助开挂!(透视)详细教程(2022已更新)(哔哩哔哩)是一款可以让一直...
第2实锤!wepoke ai外... 第2实锤!wepoke ai外挂透明挂辅助器神器,wpk如何才能稳定长期收益(有挂辅助)-哔哩哔哩;...
四分钟体悟!心悦填坑提高胜率!... 四分钟体悟!心悦填坑提高胜率!(透视)外挂辅助脚本(2024已更新)-哔哩哔哩是一款可以让一直输的玩...
1分钟了解!微扑克最新软件透明... 1分钟了解!微扑克最新软件透明挂辅助神器,微扑克到底有辅助器(有挂方针)-哔哩哔哩是一款可以让一直输...
六分钟了解!(新海狮)外挂透明... 六分钟了解!(新海狮)外挂透明挂辅助器插件!(透视)详细教程(2021已更新)(哔哩哔哩);科技安装...
3分钟秒懂!友乐广西麻将有挂的... 3分钟秒懂!友乐广西麻将有挂的的!(透视)外挂透视辅助透视挂(2021已更新)-哔哩哔哩是一款可以让...
第十个了解!微扑克轻量版外挂辅... 第十个了解!微扑克轻量版外挂辅助器透视挂,wpk微扑克有挂的(有挂讲解)-哔哩哔哩是一款可以让一直输...
7刹那秒懂!(悠闲娱乐)外挂透... 7刹那秒懂!(悠闲娱乐)外挂透视辅助脚本!(透视)详细教程(2022已更新)(哔哩哔哩);7刹那秒懂...
6分钟体悟!闽游十三水有辅助器... 自定义新版闽游十三水有辅助器的系统规律,只需要输入自己想要的开挂功能,一键便可以生成出闽游十三水有辅...
BGP路径属性 路径属性分类 1. 公认属性(所有 BGP 路由器都能识别) (1) 公...