C++搜索二叉树的实现

C++搜索二叉树的实现

1.引言

搜索二叉树又称二叉排序树,是二叉树中的经典结构。它是满足以下特点的二叉树:

1.若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

2.若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

3.它的左右子树也分别为二叉搜索树

简单来说,就是左节点的值比根节点小,右节点的值比根节点大。
在这里插入图片描述

2.目标

本次实现将模拟出一棵基本的搜索二叉树,使其具有插入、删除、查找、中序遍历等功能。此外,还要重写它的构造、拷贝构造和析构函数,使其能够按需要进行深拷贝、节点复制以及释放节点等功能。

3.实现思路

3.1 二叉树的插入

1.树为空,则直接新增节点,将其赋值给root根节点

2.树不为空,从根开始比较,要插入的值比当前节点小往左走,否则往右走,直到找到插入位置,并链接上节点

在这里插入图片描述

​ 值得关注的是,由于搜索二叉树本身不允许有冗余重复的值,所以在写插入函数Insert()的时候,最好把返回值设置成bool类型,这样有重复的值就能通过返回false显示插入失败从而驳回相同值的情况

3.2 二叉树的查找

从根开始比较,要插入的值比当前节点小往左走,否则往右走,

若与当前节点相等则找到,返回true,否则一圈找完都没找到说明该树内不存在该节点的值,返回false。

在这里插入图片描述

3.2 二叉树的删除

首先,我们二叉树删除的节点类型无外乎有以下三种

1.删除的节点没有孩子节点

2.删除的节点有一个孩子节点(只有左孩子或者右孩子)

3.删除的节点有两个孩子节点

在这里插入图片描述

第一种和第二种可以直接删,有一个孩子的节点将自己的孩子托付给自己的父亲再直接删即可。所以分类上第一种和第二种都同属一种解决办法–直接删除,有孩子的托付孩子即可。有两个孩子节点的解决办法是:间接删除–选择一个傀儡节点的值,让其值覆盖要删除结点的值,然后删除该傀儡节点。那么谁才能当选傀儡节点呢?–只有要删除节点的左子树最大值或者右子树最小值才能胜任。二者选一替代即可,我们这里采用的是选右子树的最小值。

3.3 中序遍历

我们写中序遍历(Inorder)的目的是通过中序遍历来查看这个课树是否是正常的搜索二叉树。因为一般搜索二叉树很符合中序遍历的结构,只要走一遍中序遍历就可以按照从小到大的顺序输出一遍值。

3.4 拷贝构造以及析构函数

​ 拷贝构造如果不自己重新写的话,那么默认的二叉树的拷贝构造都是浅拷贝,这往往不能很好的满足实际需求,所以我们需要在自己重新写的拷贝构造里调用递归深拷贝每个节点的函数(对应后文的**copy()**函数)来完成。

​ 析构函数也是如此,需要调用按顺序依次释放每个new出来的节点的相应函数(对应后文的**Destroy()**函数)。然后根节点置空即可。

4.具体实现

4.1节点构建BSTreeNode

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

		BSTreeNode(const K& key)  //	带参构造:通过值构建出节点
			:_left(nullptr)
			,_right(nullptr)
			,_key(key)
		{}

	};

4.2 树的基本结构构建

template<class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node;
	public:

		BSTree() = default; //强制生成默认构造


		BSTree(const BSTree<K>&t) //拷贝构造
		{
			_root = copy(t._root);    //深拷贝
		}
		~BSTree() //析构函数
		{
			Destroy(_root);
			_root = nullptr;
		}



		bool Insert(const K& key) //插入函数
		{}

		bool Find(const K& key) //查找函数
		{}

		bool Erase(const K& key) //删除函数
		{}
		void Inorder() //中序遍历
		{
			_Inorder(_root); //套个子函数,由于_root是私有成员,
            				 //防止在测试函数中越权调用_root的事情发生
		}

		protected:
			Node* copy(Node* root) //写入拷贝构造的copy节点的函数
			{}

			void Destroy(Node*& root)//写入析构函数中的销毁每个节点的函数
			{}

			void _Inorder(Node* root) 
			{}
	private:
		Node* _root = nullptr; //根节点
	};

4.3 Insert()插入函数

bool Insert(const K& key)
		{
			if (_root == nullptr) //_root为空代表是第一个插入的节点,直接new出来给root即可
			{
				_root = new Node(key);
				return true;
			}

			Node* cur = _root;
			Node* parent = nullptr; //需要父节点来判断cur位置

			while (cur) //走到合适位置
			{
				if (cur->_key < key) //插入值比该节点大,往右走
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)//插入值比该节点小,往左走
				{
					parent = cur;
					cur = cur->_left;
				}
				else // // cur->_key == key 相等返回false 不允许冗余
					return false;
			}
			 cur = new Node(key); //找到位置后开始链接
			if (parent->_key > key)
				parent->_left = cur;
			else
				parent->_right = cur;

			return true;
		}

4.4 Find()查找函数

bool Find(const K& key)
		{
			Node* cur = _root;//定义cur指针往下找

			while (cur)//往下找
			{
				if (cur->_key > key)
					cur = cur->_left;
				else if (cur->_key < key)
					cur = cur->_right;
				else // cur->_key == key
					return true;  //找到返回true
			}

			return false;
		}

4.5 Erase()删除函数

bool Erase(const K& key)
		{
			if (_root == nullptr)  //为空
				return false;

			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)  //还是正常往下找
			{
				if(cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else //cur->_key == key 说明找到要删除的那个节点了
				{
					//找到了,开始删除
					if (cur->_left == nullptr) //如果cur的左为空 (包含左右孩子都为空的情况)
					{
						if (parent->_left == cur) //并且cur还在父节点的左孩子位置上
						{
							parent->_left = cur->_right; //就把cur的右孩子交给parent的左孩子节点位置上
						}
						else // parent->_right == nullptr
						{
							parent->_right = cur->_right; //同理
						}

					}

					else if (cur->_right == nullptr)  //如果cur右孩子为空
					{
						if (parent->_left == cur) //并且cur还在父节点左孩子位置上
						{
							parent->_left = cur->_left; //酒吧cur的左孩子交给父节点左孩子位置上
						}
						else//parent->_right == cur
						{
							parent->_right = cur->_left; //同理
						}

					}
					else //删除的有两个孩子节点的节点
					{
						Node* minRight = cur->_right;  //选择找到右孩子最小值的玩法
						Node* pminRight = cur;  //需要它的父节点来确定位置

						while (minRight->_left)  //右子树的最小值即在右子树的最左边,一直往左找即可
						{
							pminRight = minRight;
							minRight = minRight->_left;
						}

						cur->_key = minRight->_key;  //将傀儡节点的值赋给cur要删除的节点

						if (pminRight->_left == minRight) //连接剩余孩子节点
						{
							pminRight->_left = minRight->_right;
						}
						else
							pminRight->_right = minRight->_right;

						delete minRight; //删除傀儡节点
					}



					return true;
				}
			}


			return false;
		}

4.6 构造函数BSTree()

BSTree() = default; //强制生成默认构造,需要写上,否则会弹出没有合适的默认构造可以用的错误


		BSTree(const BSTree<K>&t)
		{
			_root = copy(t._root);    //深拷贝
		}
Node* copy(Node* root) //拷贝构造相关函数
			{			//递归拷贝每一个节点
				if (root == nullptr)
					return nullptr;

				Node* newRoot = new Node(root->_key);
				newRoot->_left = copy(root->_left);
				newRoot->_right = copy(root->_right);
				return newRoot;  //返回新节点

			}

4.7 析构函数~BSTree()

~BSTree()
		{
			Destroy(_root); //调用destroy函数
			_root = nullptr;
		}
	void Destroy(Node*& root)  //递归删除每一个节点
			{

				if (root == nullptr)
					return;

				Destroy(root->_left);
				Destroy(root->_right);
				delete root;
				root = nullptr;
			}

5.结果展示

让我们来一组测试用例测试一下

void test_BSTree() {
		BSTree<int> bs;
		bs.Insert(8);
		bs.Insert(4);
		bs.Insert(9);
		bs.Insert(10);
		bs.Insert(3);
		bs.Insert(5);
		bs.Insert(7);
		bs.Inorder();
		cout << endl;
		bs.Erase(8); //删除根节点8后再打印结果
		bs.Inorder();
	}

在这里插入图片描述

说明成功展示出搜索二叉树了!!

如果觉得文章对你有帮助的话,麻烦动点个赞吧!!!

如果你还想关注我更多的文章内容,请访问:
一纸聊完C++继承
C++优先级队列PriorityQueue模拟实现
C++深入了解类和对象

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Arthur___Cui

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值