当前位置: 首页 > news >正文

C++ ——— 二叉搜索树

目录

二叉搜索树的概念

单个节点的结构

二叉搜索树的结构

中序遍历

插入

查找

删除

析构

拷贝构造

重载赋值运算符


二叉搜索树的概念

二叉搜索数也称为二叉排序数或者二叉查找树

二叉搜索树:一棵二叉树,可以为空;如果不为空,要满足以下性质

1. 非空左子树的所有键值小于其根节点的键值

2. 非空右子树的所有键值大于其根节点的键值

3. 左右子树都是二叉搜索树

特征:

这样的二叉搜索树通过中序遍历后是升序,因为左子树都小于根节点,右子树都大于根节点,所以也称之为二叉排序树 


单个节点的结构

struct BSTreeNode
{
	BSTreeNode(const K& key)
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}

	K _key;
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
};

左子树的值要小于根节点的值,右子树的值要大于根节点的值 


二叉搜索树的结构

template<class K>
struct BSTree
{
	typedef BSTreeNode<K> Node;

public:
    // 函数实现...

private:
	Node* root = nullptr;
};

中序遍历

private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		// 左子树 - 根 - 右子树
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}

public:
	void InOrder()
	{
		_InOrder(_root);
		
		cout << endl;
	}

因为在类外面不能访问私有变量 _root,所以在类里面实现递归时需要配合子函数来实现


插入

bool Insert(const K& key)
{
	// 判断二叉树是否为空,为空时直接插入链接
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}

	// 不为空时,需要找到合适的插入位置
	Node* parent = nullptr;
	Node* cur = _root;

	while (cur != nullptr)
	{
		parent = cur;

		if (cur->_key < key)
		{
			// 当前节点的值小于要插入的值,那么就往右节点走
			cur = cur->_right;
		}
		else if(cur->_key > key)
		{
			// 否则就往左节点走
			cur = cur->_left;
		}
		else
		{
			// 相等时就结束
			return false;
		}
	}

	// 找到合适的位置时就链接
	cur = new Node(key);

	if (parent->_key > key)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}

	return true;
}

因为规定二叉搜索树不能出现一样的值,所有在判断是否相等时直接返回 false ,证明这个值已经存在了

parent 指针指向 cur 的前一个节点,方便最后 cur 找到合适的位置时链接,在链接时,parent 指向的节点也不知道 cur 该链接在左或是右,所以需要再次比较得出结果

测试代码:


查找

bool Find(const K& kay)
{
	Node* cur = _root;

	while (cur != nullptr)
	{
		if (cur->_key > kay)
		{
			// 当前节点大于 key 时就往左节点找
			cur = cur->_left;
		}
		else if (cur->_key < kay)
		{
			// 当前节点小于 key 时就往左节点找
			cur = cur->_right;
		}
		else
		{
			// 相等就是找到了,返回 true
			return true;
		}
	}

	// 一直没有找到就返回 false
	return false;
}

同样是根据左小右大的规则来查找


删除

bool Erase(const K& key)
{
	// 先找到要删除的节点
	Node* cur = _root;
	Node* parent = nullptr;

	while (cur != nullptr)
	{
		if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			// 找到了,进行删除

			if (cur->_left == nullptr) //当要删除的节点左为空时
			{
				if (cur == _root)
				{
					// 当要删除的节点为根节点时要单独处理
					_root = cur->_right;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
				}
			}
			else if (cur->_right == nullptr) //当要删除的节点右为空时
			{
				if (cur == _root)
				{
					_root = cur->_left;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}
			}
			else //当要删除的节点左右都不为空时
			{
				// 先找到右树的最小节点(最左节点)
				Node* subLeft = cur->_right;
				Node* subParent = cur;

				while (subLeft->_left != nullptr)
				{
					subLeft = subLeft->_left;
				}

				// 交换
				std::swap(cur->_key, subLeft->_key);

				// 链接
				if (subLeft == subParent->_left)
					subParent->_left = subLeft->_right;
				else
					subParent->_right = subLeft->_right;
			}

			// 删除完成,最后返回 true
			return true;
		}
	}

	// 没有找到要删除的节点
	return false;
}

如图所示:

先通过左小右大的原则来找要删除的节点,找到后开始删除

在删除的节点里面有三种情况:

1. 要删除的节点只有左子树没有右子树

2. 要删除的节点只有右子树没有左子树

3. 要删除的节点有左右子树

叶子节点可以归纳为1、2点内

那么先拿删除 14 节点举例:

那么 14 节点就是右子树为空,创建一个 parent 节点指针指向 14 节点的父亲,也就是 10 节点,并且直接判断 14 节点是 10 节点的左还是右,因为 14 节点的右为空,所以判断后 10 节点直接链接 14 节点的左即可

需要注意的是,当要删除的节点是根节点时,就是根节点的右或者左子树为空时,需要作特殊处理,详情请见代码

删除 10 节点,10 节点是左子树为空的情况也是一样的,这里就不过多赘述

删除叶子节点也和上面的代码逻辑一样

删除 8 节点举例:

8 节点既是根节点也是左右子树都不为空的情况,那么删除的话就需要使用交换法

找出左子树中最大的节点,或者找出右子树最小的节点进行交换,这样才能成功交换,并且不影响二叉树

以上代码中我实现的是找出右子树最小的节点进行交换,所以 subLeft 就是找到右子树中最小的那个节点,也就是最左边的那个节点,subParent 指向  subLeft 的父亲节点

然后 subLeft 和要删除的节点 cur 进行交换,交换后再通过 subParent 进行链接,因为 subLeft 是右子树的最左节点,所以 subLeft 一定没有左子树,所以只需要判断 subLeft 在 subParent 的左还是右,再通过 subParent 的左或者右链接 subLeft 的右即可


析构

private:
	void Destroy(Node*& root)
	{
		// 后续递归
		if (root == nullptr)
		{
			return;
		}

		// 左子树 - 右子树 - 根
		Destroy(root->_left);
		Destroy(root->_right);

		delete root;
	}

public:
	~BSTree()
	{
		_~BSTree(_root);
	}

拷贝构造

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

public:
	BSTree(const BSTree<K>& t)
	{
		_root = copy(t._root);
	}

重载赋值运算符

BSTree<K>& operator=(BSTree<K> t)
{
	swap(_root, t._root);

	return *this;
}

文章转载自:

http://890YY5YT.yqyhr.cn
http://JvhqlkD1.yqyhr.cn
http://1qmlzgcX.yqyhr.cn
http://S857feoY.yqyhr.cn
http://x2Mf346m.yqyhr.cn
http://FrJ82pMX.yqyhr.cn
http://eUuzR6BY.yqyhr.cn
http://zFvkwXGH.yqyhr.cn
http://EtP7uTnJ.yqyhr.cn
http://GOAazeOH.yqyhr.cn
http://L6SBoKpg.yqyhr.cn
http://LVi9Fq2j.yqyhr.cn
http://mBYFgld6.yqyhr.cn
http://hRRHwJ0f.yqyhr.cn
http://5kYYHAUj.yqyhr.cn
http://aBk34jhc.yqyhr.cn
http://JrS9zTjg.yqyhr.cn
http://sQGZ7cWW.yqyhr.cn
http://HZbAVAaH.yqyhr.cn
http://ElEyzFCq.yqyhr.cn
http://MUCSyxEs.yqyhr.cn
http://KQKYbpFC.yqyhr.cn
http://heA8UbIR.yqyhr.cn
http://3Rt0KH9g.yqyhr.cn
http://80WBJFUt.yqyhr.cn
http://kdfwvK7Z.yqyhr.cn
http://sp1QwICy.yqyhr.cn
http://DrcpJ1hy.yqyhr.cn
http://vBWAXpbc.yqyhr.cn
http://ntRxmcr0.yqyhr.cn
http://www.dtcms.com/a/36079.html

相关文章:

  • EasyExcel 使用指南:基础操作与常见问题
  • MySQL 最左前缀原则:原理、应用与优化
  • Winform工具箱、属性、事件
  • 04基于vs2022的c语言笔记——数据类型
  • C# httpclient 和 Flurl.Http 的测试
  • Mesh自组网技术及应用
  • Threejs教程三【揭秘3D贴图魔法】
  • 如何使用爬虫获取淘宝商品详情:API返回值说明与案例指南
  • Unity 第三人称人物切动画时人物莫名旋转
  • 3.18 ReAct 理论实战:构建动态推理-行动循环的企业级 Agent
  • pycharm技巧--鼠标滚轮放大或缩小 Pycharm 字体大小
  • ESP8266+STM32+阿里云保姆级教程(AT指令+MQTT)
  • 2021年蓝桥杯javaB组第二场题目+部分解析
  • 软考——WWW与HTTP
  • 【R语言】ggplot2绘图常用操作
  • 安卓cmake修改版本设置路径
  • 校园的网络安全
  • GPT-4 它不仅仅是 ChatGPT 的升级版,更是人工智能的一次革命性突破!简单原理剖析
  • JSON Web Token在登陆中的使用
  • 在大数据项目中如何确保数据的质量和准确性的
  • 七.智慧城市数据治理平台架构
  • 微信小程序页面导航与路由:实现多页面跳转与数据传递
  • DeepSeek-R1:通过强化学习激发大语言模型的推理能力
  • JVM生产环境问题定位与解决实战(三):揭秘Java飞行记录器(JFR)的强大功能
  • C#开发——如何捕获异常和抛出异常
  • PHP入门基础学习五(函数1)
  • 黑客入门(网络安全术语解释)
  • DeepSeek为云厂商带来新机遇,东吴证券看好AI带动百度智能云增长
  • JVM可用的垃圾回收器
  • C++ openssl AES/CBC/PKCS7Padding 256位加密 解密示例 MD5示例