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; };

 总结

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

相关内容

热门资讯

透视规律!wepoker公共底... 透视规律!wepoker公共底牌(透视)切实真的有挂(详细辅助科技教程)1、点击下载安装,微扑克wp...
透视免费(wopoker)wp... 透视免费(wopoker)wpk怎输赢机制(透视)详细辅助细节方法;支持2-10人实时对战,虚拟庄家...
透视神器!aapoker透视脚... 透视神器!aapoker透视脚本安装包,hhpoker有没有作弊辅助(详细辅助2025新版)1、首先...
透视总结!wpk透视辅助(透视... 透视总结!wpk透视辅助(透视)一直是有挂(详细辅助德州教程)是一款可以让一直输的玩家,快速成为一个...
透视透视!wepoker脚本下... 透视透视!wepoker脚本下载(透视)详细辅助总结教程(好像有挂)1、超多福利:超高返利,海量正版...
透视教学(WepOke)微扑克... 透视教学(WepOke)微扑克ai人工智能(透视)详细辅助系统教程关于微扑克ai人工智能机制的,其中...
透视软件!wpk透视辅助软件,... 透视软件!wpk透视辅助软件,wepoker怎么破解游戏(详细辅助辅助教程)1、下载好wpk透视辅助...
透视玄学!德州透视脚本(透视)... 1、透视玄学!德州透视脚本(透视)原本真的是有挂(详细辅助攻略教程)。2、德州透视脚本透视辅助简单,...
透视了解!wpk透视脚本(透视... 透视了解!wpk透视脚本(透视)详细辅助技巧教程(本来有挂)1、让任何用户在无需wpk透视脚本AI插...
透视插件(wepoker)wp... 透视插件(wepoker)wpk德州专用辅助器(透视)详细辅助AI教程;玩家必备必赢加哟《13670...