【C++】模拟实现二叉搜索(排序)树

news2024/9/21 22:26:19

🦄个人主页:修修修也

🎏所属专栏:实战项目集

⚙️操作环境:Visual Studio 2022


目录

一.了解项目功能

二.逐步实现项目功能模块及其逻辑详解

📌实现BSTreeNode类模板

🎏构造BSTreeNode类成员变量

🎏实现BSTreeNode类构造函数

📌实现BSTree类模板

🎏构造BSTree类成员变量

🎏实现BSTree类构造函数

🎏实现BSTree类InOrder()函数

🎏实现BSTree类Find()函数

🕹️循环版Find()函数

🕹️递归版FindR()函数

🎏实现BSTree类Insert()函数

🕹️循环版Insert()函数

🕹️递归版InsertR()函数

🎏实现BSTree类Erase()函数

🕹️循环版Erase()函数

🕹️递归版EraseR()函数

🎏实现BSTree类析构函数

🎏实现BSTree类拷贝构造函数

🎏实现BSTree类赋值运算符重载operator=函数

三.项目完整代码

📌test.cpp文件

📌BinarySearchTree.h文件

结语


一.了解项目功能

        在本次项目中我们的目标是实现一个二叉搜索树(Binary Search Tree)类模板,还不了解二叉搜索树概念的朋友可以先移步[【数据结构】什么是二叉搜索(排序)树?] 其结构图示如下:

        二叉搜索树结点(BSTreeNode)需要包含三个要素:键值_key,左指针域_left,右指针域_right.结点(BSTreeNode)逻辑结构图示如下:

        二叉搜索树需要实现的功能有:

  1. 构造函数
  2. 中序遍历InOrder()函数
  3. 查找函数Find()--循环版 + FindR()--递归版
  4. 插入函数Insert()--循环版 + InsertR()--递归版
  5. 删除函数Erase()--循环版 + EraseR()--递归版
  6. 析构函数
  7. 拷贝构造函数
  8. 赋值运算符重载operator=函数

二.逐步实现项目功能模块及其逻辑详解

通过第二部分对项目功能的介绍,我们已经对  的功能有了大致的了解,虽然看似需要实现的功能很多,貌似一时间不知该如何下手,但我们可以分步分模块来分析这个项目的流程,最后再将各部分进行整合,所以大家不用担心,跟着我一步一步分析吧!


!!!注意,该部分的代码只是为了详细介绍某一部分的项目实现逻辑,故可能会删减一些与该部分不相关的代码以便大家理解,需要查看或拷贝完整详细代码的朋友可以移步本文第四部分。


📌实现BSTreeNode类模板

🎏构造BSTreeNode类成员变量

        我们在一开始需求分析时就已经明确了BSTreeNode类的成员变量组成包含三个要素:键值_key,左指针域_left,右指针域_right.结点(BSTreeNode),其逻辑结构图示如下:

        这里还有一个小的点需要提一下,我们在这里实现的BSTreeNode类,后续是要给BSTree类使用的,考虑到后续我们在二叉搜索树的操作函数中会有直接操作结点成员变量的情况,如:

root = root->_right; //相当于在BSTreeNode外部直接通过类指针访问了类成员变量_right

        基于class的封装特性,class的成员变量一般都是默认为私有的,如果我们要允许其他类直接访问class的成员变量和函数,就要将其都设置为public,或者通过友元/内部类来解决成员访问问题.

        既然如此,我们不妨直接使用struct定义结点成员变量和函数,因为struct定义的类的成员变量和函数默认就是公有的,完全可以满足我们的需求.

        综上所属,该部分代码如下:

template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;
};

🎏实现BSTreeNode类构造函数

        BSTreeNode的构造函数我们实现两个即可,一个是有参构造,一个是无参构造,而无参构造又可以通过给缺省值的方式和有参构造合二为一,所以我们用初始化列表来实现一下BSTreeNode的构造函数:

//缺省值的作用是在无参调用时直接去调用模板实例化的类的无参构造函数
//这里一定不能将缺省值给0/nullptr!因为你不知道模板实例化的类具体到底是内置类型还是自定义类型(如Date)
//所以要显示调用K类型它自己的无参构造函数
BSTreeNode(const K& key = K())
	:_left(nullptr)
	,_right(nullptr)
	,_key(key)
{}

📌实现BSTree类模板

🎏构造BSTree类成员变量

        BSTree类成员变量比较简单,就是一个根节点的指针,为了简化模板中出现的BSTreeNode<K>类型的名字,我们将其简化命名为:Node

        该部分实现代码如下:

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;  //简化命名
public:
    //成员函数

private:
    //成员变量or成员函数子函数
    Node* _root;
};

🎏实现BSTree类构造函数

         BSTree类的构造函数非常简单,因为只有一个成员变量根节点指针_root,所以我们用初始化列表将该指针初始话为nullptr即可,代码如下:

BSTree()
	:_root(nullptr)
{}

🎏实现BSTree类InOrder()函数

         BSTree类的中序遍历逻辑和二叉树的中序遍历逻辑一模一样,即

  1. 先递归访问左子树
  2. 再访问根节点
  3. 最后递归访问右子树

        但在面向对象设计中,我们设计类内的递归函数会遇到一个问题,就是类对象调用其成员函数的递归传参问题:

        我们知道C语言完成中序遍历函数时一个参数是恰好可以满足首次调用和递归调用时传参的形式的:

        但是在面向对象语言中,这个参数是不好处理的,因为首次调用时不需要传参,类对象调用成员函数会默认传this指针,所以不需要传参数,但后续递归调用又需要传参数,这就导致这个函数参数有也不对,没有也不对:

        所以好的解决方式是将递归主体封装成子函数,代替成员函数进行递归操作,这种方式我们在后续完成递归成员函数时会一直用到,大家一定要习惯。

        综上所述,该部分实现代码如下:

//中序遍历函数
void InOrder()
{
	_InOrder(_root);  //代替成员函数完成递归
    cout<<endl;       //方便后续观察测试用例
}

//中序遍历子递归函数
void _InOrder(Node* root)
{
	if (root == nullptr)
	{
		return;
    }

	_InOrder(root->_left);       //递归访问左子树
	cout << root->_key << " ";   //访问根节点
	_InOrder(root->_right);      //递归访问右子树
}

🎏实现BSTree类Find()函数

        BSTree类的查找函数实现思路如下:

  1. 从根节点开始比较查找, 如果查找值比根结点值大,则往右子树继续查找; 如果查找值比根结点值小,则往左子树继续查找;
  2. 最多查找高度次, 即查找到叶子节点, 如果还没找到, 则该值不存在于此树中。

        思路图示如下:


🕹️循环版Find()函数

        循环版Find()函数主要就是借助cur指针变量在二叉搜索树中一直查找,直到找到key值或者找到nullptr值为止,前者代表找到了,后者表示没有找到.

        综上,实现代码如下:

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

🕹️递归版FindR()函数

        递归版FindR()函数思路主要是根据key值递归去根的左或右子树继续查找,如果找到nullptr值,代表没有找到,就递归回false,如果找到了key值,就递归回true.

        综上,实现代码如下:

//递归FindR成员函数
bool FindR(const K& key)
{
	return _FindR(_root, key);
}

//FindR()函数子递归
bool _FindR(Node* root, const K& key)
{
	if (root == nullptr)
	{
		return false;
	}

	if (root->_key < key)
	{
		_FindR(root->_right, key);   //根小于key,递归查找右子树
	}
	else if (root->_key > key)
	{
		_FindR(root->_left, key);    //根大于key,递归查找左子树
	}
	else
	{
		return true;          //根等于key,表示找到了
	}
}

🎏实现BSTree类Insert()函数

        BSTree类的插入函数实现思路如下:

  1. 树为空,则直接新增节点,赋值给根节点指针
  2. 树不为空,则按二叉搜索树性质查找插入位置, 在查找到为空的位置插入新增值, 如果查找到该值已存在, 则返回查找失败(二叉搜索树不允许插入重复值)

        思路图示如下:


🕹️循环版Insert()函数

        循环版Insert()函数思路就是创建cur找插入位置,然后parent记录cur的父亲结点,方便找到后的插入工作,逻辑不是很难,细节要注意,实现代码及注释如下:

bool Insert(const K& key)
{ 
    //根节点为空直接令_root等于新插结点
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
    
    //开始在子树里面找插入位置cur,同时创建parent记录cur的父节点
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)    //查找位置key值比新插key值小,向右继续找插入位置
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)   //查找位置key值比新插key值大,向左继续找插入位置
		{
			parent = cur;
			cur = cur->_left;
		}
		else            //查找位置key值和新插key值相等,表明新插key值已经存在于树中了
		{
			return false;    //就不用再插入,直接返回插入失败
		}
	}
    
    //运行到此表示找到插入位置了,下面创建新结点,然后将其链接父节点即可
	cur = new Node(key);

	if (parent->_key < key)    //判断新结点和父节点的大小,大于就链接父节点的右指针
	{
		parent->_right = cur;
	}
	else         //小于就链接父节点的左指针
	{
		parent->_left = cur;
	}
	return true;
}

🕹️递归版InsertR()函数

        递归InsertR()函数的逻辑是,如果新插key值大于根节点key值,就递归右子树继续插入,如果新插key值小于根节点key值,就递归左子树继续插入,如果相等就返回插入失败.

        逻辑很简单,代码如下:

bool InsertR(const K& key)
{
	return _InsertR(_root, key);
}

bool _InsertR(Node*& root,const K& key)
{
	if (root == nullptr)    //找到nullptr表示找到插入位置了,直接插入即可
	{
		root = new Node(key);//因为子递归的参数是引用,所以可以直接将找到的空位置改为新结点
		return true;
	}

	if (root->_key < key)        //找到的位置key值比新插key值小,往右继续找位置
	{
		_InsertR(root->_right, key);
	}
	else if (root->_key > key)    //找到的位置key值比新插key值大,往左继续找位置
	{
		_InsertR(root->_left, key);
	}
	else        //找到的位置key值和新插入key值相等,返回插入失败
	{
		return false;
	}
}

🎏实现BSTree类Erase()函数

        BSTree类的删除函数实现思路如下:

        查找元素是否在二叉搜索中,如果不存在,则返回,如果存在,则待删除结点可能存在以下四种情况:

  1. 待删除结点无孩子结点
  2. 待删除结点只有左孩子结点(不管右孩子)
  3. 待删除结点只有右孩子结点(不管左孩子)
  4. 待删除结点左,右孩子结点都有

        在实际删除操作中,可以将1和2 / 3合并起来,因为我们的逻辑是哪边有孩子就管理,没有孩子就可以不管,因此无孩子结点既可以和不管右孩子结点情况合并又可以和不管左孩子情况合并,合并后实际的删除情况及过程如下三种:

  1. 删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
  2. 删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
  3. 在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除


🕹️循环版Erase()函数

        循环版Erase()函数实现如下,具体细节见代码注释:

bool Erase(const K& key)
{
	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            //找到了,删
		{
			if (cur->_left == nullptr)   //左空托右孤,注意左右都空也因为满足左空走这里
			{
				if (cur == _root)     //是根节点就没有父节点,直接让右孩子做根节点
				{
					_root = cur->_right;
				}
				else
				{
					if (parent->_right == cur)   //是父的右孩子就托孤到父亲的右指针
					{
						parent->_right = cur->_right;
					}
					else        //是父的左孩子就托孤到父亲的左指针
					{
						parent->_left = cur->_right;
					}
				}
			}
			else if (cur->_right == nullptr)    //右空托左孤
			{
				if (cur == _root)    //是根节点就没有父节点,直接让左孩子做根节点
				{
					_root = cur->_left;
				}
				else
				{
					if (parent->_right == cur)    //是父的右孩子就托孤到父亲的右指针
					{
						parent->_right = cur->_left;
					}
					else         //是父的左孩子就托孤到父亲的左指针
					{
						parent->_left = cur->_left;
					}
				}
			}
			else        //左右都不为空
			{
				//找左树的最右值
				Node* parent = cur;  //这里不能写=nullptr,防止删的节点的leftMax就是自己的左子树的情况,这样就不会进下面的while循环,parent没有被赋值,就会导致在9行后的if判断里出现对空指针解引用
				Node* leftMax = cur->_left;
				while (leftMax->_right)
				{
					parent = leftMax;
					leftMax = leftMax->_right;
				}

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

				if (parent->_left == leftMax)
				{
					parent->_left = leftMax->_left;
				}
				else
				{
					parent->_right = leftMax->_left;
				}

				cur = leftMax;
			}
            
            //走到这里表示待删结点的孩子已经处理好了,可以直接释放了
			delete cur;
			return true;
		}
	}
	return false;
}
🕹️递归版EraseR()函数

        递归版EraseR()函数实现如下,具体细节见代码注释:

bool EraseR(const K& key)
{
	return _EraseR(_root, key);
}

bool _EraseR(Node*& root, const K& key)
{
	if (root == nullptr)    //递归到空值表示待删元素不存在,返回删除失败
	{
		return false;
	}

	if (root->_key < key)    //小了,向右继续递归删
	{
		_EraseR(root->_right, key);
	}
	else if (root->_key > key)    //大了,向左继续递归删
	{
		_EraseR(root->_left, key);
	}
	else        //找到了,开始按三种情况删
	{
		Node*  del = root; //因为我们后面会更改root,防止delete时找不到待删元素,先记录下
		
		if (root->_left == nullptr)    //左空托右孤
		{
			root = root->_right;    //因为是引用,所以可以直接将自己改为右孩子
		}
		else if (root->_right == nullptr)    //右空托左孤
		{
			root = root->_left;    //因为是引用,所以可以直接将自己改为左孩子
		}
		else        //都有找接班
		{
			//找替代结点(左子树的最右值)
			Node* leftMax = root->_left;
			while (leftMax->_right)
			{
				leftMax = leftMax->_right;
			}
            
            //交换自己和接班的位置
			swap(root->_key, leftMax->_key);

            //然后复用递归删除自己就行,但是注意,因为自己和左子树的最右值,
            //即比自己小一点点的值交换了位置,那么再找自己删自己的时候就不能
            //从根节点开始找,因为对新根来说自己比它大一点那应该在右子树去找,
            //但实际自己被换到了左子树,所以应该一开始就在根结点的左子树找自己
			return _EraseR(root->_left, key);
		}
        
        //走到此代表待删结点的孩子已经处理好了,可以释放空间了
		delete del;
		return true;
	}
}

🎏实现BSTree类析构函数

        由于析构函数是按后序遍历递归删除整颗树的,所以我们给析构函数也创建一个子函数, 按先左,再右,再根的顺序递归删除二叉树即可,实现代码及注释如下:

//析构函数
~BSTree()
{
	Destroy(_root);
}

//析构函数子递归
void Destroy(Node*& root)
{
	if (root == nullptr)
		return;

	Destroy(root->_left);    //先递归删左子树
	Destroy(root->_right);    //再递归删右子树

	delete root;        //最后删掉根节点
	root == nullptr;    //用引用传参还可以直接在这里将root置为nullptr
}

🎏实现BSTree类拷贝构造函数

        由于拷贝构造函数是按先序遍历递归拷贝整颗树的,所以我们给拷贝构造函数也创建一个子函数, 按先根,再左,再右的顺序递归拷贝整颗二叉树即可,实现代码及注释如下:

//拷贝构造
BSTree(const BSTree<K>& t)
{
	_root = Copy(t._root);
}

//拷贝构造子函数
Node* Copy(Node* root)
{
	if (root == nullptr)    //如果待拷贝结点为空就直接返回给上层空结点即可
		return nullptr;

	Node* copynode = new Node(root->_key);    //先拷贝根节点

	copynode->_left = Copy(root->_left);      //再递归链接左子树

	copynode->_right = Copy(root->_right);    //后递归链接右子树

	return copynode;        //注意最后要将当前结点返回给上一层链接
}

🎏实现BSTree类赋值运算符重载operator=函数

        赋值运算符重载函数我们照例像之前实现STL容器那样偷个懒,直接借用形参是实参的一份临时拷贝这一特点,将this指针和待赋值的参数的形参做交换,这样就可以无痛完成赋值运算了,实现代码如下:

//赋值
BSTree<K>& operator=(BSTree<K> t)
{
	swap(_root, t._root);
	return *this;
}

三.项目完整代码

我们将程序运行的代码分别在两个工程文件中编辑,完整代码如下:

📌test.cpp文件

        该部分文件主要包含部分二叉搜索树功能测试代码,大家可以酌情参考。

#include<iostream>
using namespace std;

#include"BinarySearchTree.h"

//测试插入删除函数
void testBSTree1()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	BSTree<int> t;

	for (auto e : a)
	{
		t.Insert(e);
	}
	t.InOrder();

	t.Erase(4);
	t.InOrder();

	t.Erase(6);
	t.InOrder();

	t.Erase(7);
	t.InOrder();

	t.Erase(8);
	t.InOrder();

	for (auto e : a)
	{
		t.Erase(e);
	}
	t.InOrder();
}

//测试递归插入删除函数
void testBSTree2()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	BSTree<int> t;

	for (auto e : a)
	{
		t.InsertR(e);
	}
	t.InOrder();

	t.EraseR(4);
	t.InOrder();

	t.EraseR(6);
	t.InOrder();

	t.EraseR(7);
	t.InOrder();

	t.EraseR(8);
	t.InOrder();

	for (auto e : a)
	{
		t.EraseR(e);
	}
	t.InOrder();
}

void testBSTree3()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	BSTree<int> t;

	for (auto e : a)
	{
		t.InsertR(e);
	}
	t.InOrder();

	//测试拷贝构造
	BSTree<int> t1(t);
	t1.InOrder();
}

void testBSTree4()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	BSTree<int> t;

	for (auto e : a)
	{
		t.InsertR(e);
	}
	t.InOrder();

	//测试赋值运算符重载
	BSTree<int> t1 = t;
	t1.InOrder();
}
int main()
{
	testBSTree1();
	return 0;
}

📌BinarySearchTree.h文件

#pragma once

template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

	//缺省值的作用是在无参调用时直接去调用模板实例化的类的无参构造函数
	//这里一定不能将缺省值给0/nullptr!因为你不知道模板实例化的类具体到底是内置类型还是自定义类型(如Date)
	//所以要显示调用T类型它自己的无参构造函数
	BSTreeNode(const K& key = K())
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
};

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	BSTree()
		:_root(nullptr)
	{}

	//拷贝构造
	BSTree(const BSTree<K>& t)
	{
		_root = Copy(t._root);
	}

	//赋值
	BSTree<K>& operator=(BSTree<K> t)
	{
		swap(_root, t._root);
		return *this;
	}

	~BSTree()
	{
		Destroy(_root);
	}

	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 (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		return true;
	}

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


	//删除,四个场景
	//1.叶子  2. 一个孩子     托孤
	//3. 两个孩子      替换法:左子树的最大(右)结点,或者右子树的最小(左)节点
	bool Erase(const K& key)
	{
		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
			{
				//找到了,删
				if (cur->_left == nullptr)//左空托右孤,注意左右都空也走这里
				{
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_right == cur)
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}
					}
				}
				else if (cur->_right == nullptr)//右空托左孤
				{
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_right == cur)
						{
							parent->_right = cur->_left;
						}
						else
						{
							parent->_left = cur->_left;
						}
					}
				}
				else//左右都不为空
				{
					//找左树的最右值
					Node* parent = cur;//写nullptr必崩
					Node* leftMax = cur->_left;
					while (leftMax->_right)
					{
						parent = leftMax;
						leftMax = leftMax->_right;
					}

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

					if (parent->_left == leftMax)
					{
						parent->_left = leftMax->_left;
					}
					else
					{
						parent->_right = leftMax->_left;
					}

					cur = leftMax;
				}
				delete cur;
				return true;
			}
		}
		return false;
	}


	void InOrder()
	{
		_InOrder(_root);
	}

	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}

	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}

	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}

private:
	//将部分递归子函数放在这里,对外更好的封装这个类模板

	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* copynode = new Node(root->_key);
		copynode->_left = Copy(root->_left);
		copynode->_right = Copy(root->_right);
		return copynode;
	}

	//析构函数子递归
	void Destroy(Node*& root)
	{
		if (root == nullptr)
			return;
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
		root == nullptr;
	}

	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}

		if (root->_key < key)
		{
			_EraseR(root->_right, key);
		}
		else if (root->_key > key)
		{
			_EraseR(root->_left, key);
		}
		else//画图吧,不多说了
		{
			//找到了,开始按三种情况删
			Node*  del = root;
		
			//左空托右孤,右空托左孤,都有找接班
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else
			{
				//找替代结点
				Node* leftMax = root->_left;
				while (leftMax->_right)
				{
					leftMax = leftMax->_right;
				}

				swap(root->_key, leftMax->_key);

				return _EraseR(root->_left, key);
			}

			delete del;
			return true;
		}
	}

	bool _InsertR(Node*& root,const K& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		if (root->_key < key)
		{
			_InsertR(root->_right, key);
		}
		else if (root->_key > key)
		{
			_InsertR(root->_left, key);
		}
		else
		{
			return false;
		}
	}

	//递归Find子递归
	bool _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}

		if (root->_key < key)
		{
			_FindR(root->_right, key);
		}
		else if (root->_key > key)
		{
			_FindR(root->_left, key);
		}
		else
		{
			return true;
		}
	}

	//中序遍历子递归
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			//cout << "NULL" << endl;
			return;
		}

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

	Node* _root;
};

结语

希望这篇 二叉搜索(排序)树 的模拟实现详解能对大家有所帮助,欢迎大佬们留言或私信与我交流.

学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!

相关文章推荐

【数据结构】什么是二叉搜索(排序)树?

【数据结构】C语言实现链式二叉树(附完整运行代码)

【C++】模拟实现priority_queue(优先级队列)

【C++】模拟实现queue

【C++】模拟实现stack

【C++】模拟实现list

【C++】模拟实现vector

【C++】模拟实现string类

【C++】构建第一个C++类:Date类

【C++】类的六大默认成员函数及其特性(万字详解)

【C++】什么是类与对象?


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2153452.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

空间解析几何2:空间中两线段/直线的距离【附MATLAB代码】

目录 理论公式 MATLAB代码 理论公式 MATLAB代码 公式实现 function [dis,P,Q,t1,s1]line2LineDistance(A1,B1,C1,D1) %求两线段的最短距离 % input % A1,B1为线段一的两端点 C1,D1为线段二的两端点 % output % dis,为两线段的最短距离&#xff0c;P,Q为距离最短时在两线段上…

10.2 溪降技术:双重检查

目录 10.2 双重检查概览观看视频课程电子书&#xff1a;双重检查场景场景 1场景 2 个人责任示例 1示例 2 总结 10.2 双重检查 概览 俗话说&#xff1a;“江山易改&#xff0c;本性难移”。在我们开始体验峡谷探险时&#xff0c;培养良好的习惯对我们的进一步发展至关重要。在所…

Spring AOP的应用

目录 1、maven坐标配置与xml头配置 2、代理方式的选择与配置 3、AOP的三种配置方式 3.1、XML模式 3.1.1 创建目标类和方法 3.1.2 创建切面 3.1.3 切面xml配置与表达式说明 3.1.4 单测 3.2 纯注解模式 3.2.1 开启注解相关配置 3.2.2 创建目标类和方法 3.2.3 创建切面…

ChatGPT 4o 使用指南 (9月更新)

首先基础知识还是要介绍得~ 一、模型知识&#xff1a; GPT-4o&#xff1a;最新的版本模型&#xff0c;支持视觉等多模态&#xff0c;OpenAI 文档中已经更新了 GPT-4o 的介绍&#xff1a;128k 上下文&#xff0c;训练截止 2023 年 10 月&#xff08;作为对比&#xff0c;GPT-4…

java之斗地主部分功能的实现

今天我们要实现斗地主中发牌和洗牌这两个功能&#xff0c;该如何去实现呢&#xff1f; 1.创建牌类&#xff1a;52张牌每一张牌包含两个属性:牌的大小和牌的花色。 故我们优先创建一个牌的类(Card)&#xff1a;包含大小和花色。 public class Card { //单张牌的大小及类型/…

20240921在友善之臂的NanoPC-T6开发板上使用Rockchip原厂的Android12适配宸芯的数传模块CX6602N

127|console:/ # uname -a console:/ # ifconfig console:/ # ifconfig -a console:/ # ifconfig -a 130|console:/ # ifconfig usb0 192.168.42.130 console:/ # console:/ # ifconfig console:/ # iperf3 -s & iperf3 -c 192.168.42.130 -i 1 -t 30 20240921在友善之臂的…

828华为云征文|华为云Flexus云服务器X实例之openEuler系统下部署Grav内容管理系统

828华为云征文&#xff5c;华为云Flexus云服务器X实例之openEuler系统下部署Grav内容管理系统 前言一、Flexus云服务器X实例介绍1.1 Flexus云服务器X实例简介1.2 Flexus云服务器X实例特点1.3 Flexus云服务器X实例使用场景 二、Grav介绍2.1 CMS介绍2.2 Grav简介2.3 Grav特点2.4 …

TinyML-On-The-Fly: 实时、低功耗、低成本的微控制器嵌入式设备内计算机视觉技术用于无人机图像分类

这篇论文的标题是《TinyML-On-The-Fly: Real-Time Low-Power and Low-Cost MCU-Embedded On-Device Computer Vision for Aerial Image Classification》&#xff0c;作者是 Riya Samanta, Bidyut Saha, Soumya K. Ghosh&#xff0c;来自印度理工学院克勒格布尔分校。论文主要研…

电子元器件之MOS管,附上几个常用MOS管电路和仿真。

MOS管是一种常用的电子元器件。 1.MOS管的类别 MOSFET简称MOS&#xff0c;是一种绝缘栅型场效应管。按照类别可以分为增强型mos管和耗尽型mos管。 导电沟道的形成方式‌ 增强型MOS管&#xff1a;在没有外加电压时&#xff0c;源极和漏极之间没有导电沟道存在。只有当栅极电…

【玉米田】

题目 代码 #include <bits/stdc.h> using namespace std; typedef long long LL;const int mod 1e8; const int M 1 << 12; LL f[13][M]; int g[13]; vector<int> state; vector<int> p[M]; int n, m; bool check(int x) {return !(x & x <&…

攻防世界---->Windows_Reverse1(补)

做题笔记。 做题回顾。 假设&#xff0c;我们不知道地址随机怎么办&#xff1f;不能动调&#xff0c;只能静态分析。 下载 查壳 upx脱壳。 32ida打开。 动调报错。 重新打开&#xff0c;静态分析。 跟进关键函数。 不明白可以反汇编和汇编一起看。 溯源。 *decode 取值等于 by…

分布式锁之 防误删(优化之UUID防误删)

文章目录 1、AlbumInfoApiController --》testLock()2、AlbumInfoServiceImpl --》testLock()3、问题&#xff1a;删除操作缺乏原子性。 实现如下&#xff1a; 1、AlbumInfoApiController --》testLock() Tag(name "专辑管理") RestController RequestMapping(&quo…

【计网】从零开始掌握序列化与反序列化 --- 基础知识储备与程序重构

从零开始掌握序列化与反序列化 1 初识序列化与反序列化2 再谈Tcp协议3 程序重构3.1 Socket类3.2 回调函数设计3.3 最终的Tcp服务器类 1 初识序列化与反序列化 在刚学习计算机网络时&#xff0c;我们谈到过网络协议栈&#xff0c;其中最上层的就是应用层&#xff0c;那么这个应…

Qt圆角窗口

Qt圆角窗口 问题&#xff1a;自己重写了一个窗口&#xff0c;发现用qss设置圆角了&#xff0c;但是都不生效&#xff0c;不过子窗口圆角都生效了。 无边框移动窗口 bool eventFilter(QObject *watched, QEvent *evt) {static QPoint mousePoint;static bool mousePressed f…

开源、极简的B站第三方,建议所有人收藏

很多人说B站落寞了&#xff0c;但我觉得不是B站落寞&#xff0c;而是长视频落寞了。现代人已经没有充足的耐心&#xff0c;刷完一个十分钟的视频。毕竟&#xff0c;短视频可以把这十分钟切成50份&#xff0c;让我们开心50次。 可怕的是&#xff0c;B站即使落寞&#xff0c;在长…

继承的例题

答案&#xff1a;D 解析&#xff1a;C允许一个子类继承多个父类 知识点&#xff1a; 子类是父类的特殊化&#xff0c;父类是子类的泛化 解析&#xff1a;子类可以共享父类的属性和方法&#xff0c;选项A正确 面向对象关系中&#xff0c;类与类的关系包含继承&#xff0c;包…

IntelliJ IDEA 2024.1.4 (Ultimate Edition)找不到Add Framework Support解决方法

目录 背景: 解决方法&#xff1a; 步骤1: 步骤2&#xff1a; 步骤3&#xff1a; 创建Web项目的完整流程&#xff1a; 步骤1: 步骤2: 步骤3&#xff1a; 步骤4&#xff1a; Web优点: 背景: 我的IDE版本是IntelliJ IDEA 2024.1.4 (Ultimate Edition)&#xff0c;当我…

【优选算法之双指针】No.2--- 经典双指针算法(下)

文章目录 前言一、双指针示例&#xff1a;1.1 ⽔果成篮1.2 和为s的两个数字1.3 三数之和1.4 四数之和 二、双指针总结&#xff1a; 前言 &#x1f467;个人主页&#xff1a;小沈YO. &#x1f61a;小编介绍&#xff1a;欢迎来到我的乱七八糟小星球&#x1f31d; &#x1f4cb;专…

前后端分离,使用MOCK进行数据模拟开发,让前端攻城师独立于后端进行开发

mock是什么 Mock生成随机数据,拦截Ajax 请求&#xff0c;前后端分离&#xff0c;让前端攻城师独立于后端进行开发。 增加单元测试的真实性 通过随机数据,模拟各种场景。 在实际开发过程中&#xff0c;前端是通过axios来请求数据的&#xff0c;很多时候前端开发者就是通过写固定…

【Git必看系列】—— Git巨好用的神器之git stash篇

应用场景 当我们开发一个新功能时会先从master拉出一个分支dev&#xff0c;然后在这个dev分支下吭哧吭哧的开始写代码开发新功能&#xff0c;就如下代码所示&#xff0c;我们在dev分支下开发Person类的新功能getId() public class Person {private int id;private String nam…