C++ 搜索二叉树(BST)模拟实现与应用

目录

 (一)搜索二叉树 分析

1.结构:

2. 性能分析:

2.1BST与二分查找:

2.2树的平衡性将影响BST的性能。

(二)搜索二叉树实现

1.非递归实现

1.1查找

1.2插入

1.3定点删减

1.4 中序遍历(输出排序)

2.递归实现

2.1 递归与非递归版本对比

2.2  关键点分析

2.3代码实现

(三)二叉搜索树应用

1.场景一:用于排序《key模型》

2.场景二:用于搜索《key--value模型》

(四)源码分享

1.二叉搜索树非递归版本

2.key-value模型

                                      如有错误请多多指正,谢谢观看!

 (一)搜索二叉树 分析

1.结构:

  •  搜索二叉树主要功能有:搜索,增删,排序(中序遍历)。
  •  搜索二叉树的存储特点:在二叉树的基础上,左节点的键总是小于父节点的键,右节点的键总是大于父节点的键。在深层意义上,我们存储的数据需要有一个键(以此键作为标准来排序)。
  • 搜索二叉树中nullptr也被看成一个节点,这一点在递归中有很大妙用。
  • 父子节点的继承关系:我们假设 3为父节点,删除了3,父节点的位置将要由它的子节点继承,但这不改变子节点与它下一代节点的继承方式,问题的关键:“到底是左孩子节点还是右孩子节点顶替了父节点3"。这是插入和删除节点的时候我们最关心的问题。多继承问题是复杂的 ,所以我们将其转化为单继承问题解决,即”左/右孩子顶替了父节点“两种情况讨论。其他情况也划归成这两种情况。

2. 性能分析:

2.1BST与二分查找:

      谈起二分查找,在排序中算是很优秀的搜索算法,但是二分查找的条件要求存储数据必须是有序的数组/链表,然而对于一批维护的数据需要伴随着频繁的增删改查,以及排序算法多次调用带来的代价也就大了起来,随之程序的效率就会大大下降。而BST更像二分查找的存储升级版,可以有序的增删数据

      相比之下,BST的存储和搜索就很均衡。BST在搜索中表现得也很好,每向下移动一个深度,就消除大约50%的潜在节点(接近O(log n))。比如我们要找到2,我们就可以从根(5)开始,向左移动,意味着我们立刻消除了4个可能的节点。继续向1移动,我们又消除了一个可能的节点。然后在1的右面我们找到了节点,只通过了3步实现了的节点。

2.2树的平衡性将影响BST的性能。

 这个例子就很极端,当树不是最优或不平衡时候,BST的搜索性变差,O(n)可能成为最坏的情况。所以根据键值选取合适的根节点,调整存储顺序是很重要的因素。


(二)搜索二叉树实现

1.非递归实现

1.1查找

	Node* 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 cur;
			}
		}
		return nullptr;
	}

1.2插入

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

1.3定点删除

	bool  Erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;

		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else//找到了,开始删除
			{
                //1.左为空或者右为空,把另一个孩子交给父亲管理,删除自己

				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;
						}
					}
				}
				else //2.左右孩子都不为空
				{
					//找到右树的最小节点去替代删除
					Node* minRightParent = cur;
					Node* minRight = cur->_right;
					while (minRight->_left)
					{
						minRightParent = minRight;
						minRight = minRight->_left;
					}
					cur->_key = minRight->_key;
                    //将右孩子交给父节点管理
					if (minRight == minRightParent->_left)
					{
						minRightParent->_left = minRight->_right;
					}
					else
					{
						minRightParent->_right = minRight->_right;
					}
					delete minRight;
					return true;
				}
			}
		}
		return false;
	}

1.4 中序遍历(输出排序)

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
    void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

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

2.递归实现

2.1 递归与非递归版本对比

1.一般工程中不建议使用非递归版本,额外开销很大。

2.非递归版本和递归版本间可以相互转化。

3.递归关注状态的分析。

2.2  关键点分析

递归的特点:一个子过程结束后自动链接下一个子过程。可以回溯到上一个节点,也可以传递到下一个节点。

   当前    _root节点A         子过程进行中
   接下来  _root->right节点B  子过程未进行  可以调用节点A,B,C
   接下来  _root->right节点C  子过程未进行  可以调用节点B,C....

2.3代码实现

1.插入

		bool _InsertR(Node*& root, const K& key)
		{
			if (root == NULL)  // 插入
			{
				root = new Node(key);
				return true;
			}

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

2.查找

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

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

 3.删除:

在右树查找并删掉替换节点,然后将最小值赋值给父节点。

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

			if (root->_key < key)
			{
				return _EraseR(root->_right, key);
			}
			else if (root->_key > key)
			{
				return _EraseR(root->_left, key);
			}
			else
			{
				// 找到了,root就是要删除的节点
				if (root->_left == nullptr)
				{
					Node* del = root;
					root = root->_right;
					delete del;
				}
				else if (root->_right == nullptr)
				{
					Node* del = root;
					root = root->_left;
					delete del;
				}
				else
				{
					Node* minRight = root->_right;
					while (minRight->_left)
					{
						minRight = minRight->_left;
					}
					K min = minRight->_key;

					// 转换成在root的右子树删除min
					_EraseR(root->_right, min);
					root->_key = min;
				}

				return true;
			}
		}

(三)二叉搜索树应用

1.场景一:用于排序《key模型》

      key模型,就是唯一属性作为键值,完成搜索和存储。要求唯一键具有"可比性"。主要应用场景是对一组不重复数据,进行搜索,排序,增删等操作。key模型的单一属性,决定被操作数据不可以重复

2.场景二:用于搜索《key--value模型》

      key-value模型,要求设置唯一键,对其余属性数据进行查找,增删,排序等操作。应用场景:对于英文词典,汉译英,英译汉都是key-value场景。再比如我们可以用key-value模型统计重复元素出现的次数。同时,key-value允许被操作的数据重复。

1.定义:

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

		K _key;
		V _value;

		BSTreeNode(const K& key, const V& value)
			: _left(nullptr)
			, _right(nullptr)
			, _key(key)
			, _value(value)
		{}
	};

2.查找:

		Node* Find(const K& key)
	{
		Node* cur = _root;
		if (_root == nullptr)
		{
			return nullptr;
		}
		while (cur)
		{
			if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else if(cur->_key<key)
			{
				cur = cur->_right;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

3. 插入:

		V& operator[](const K& key)
	{
		pair<Node*, bool> ret = Insert(key, V());
		return ret.first->_val;
	}
    pair<Node*, bool>  Insert(const K& key, const V& value)
	{	
		if (_root == nullptr)
		{
			_root = new Node(key, value);
			return make_pair(_root, true);
		}
		//查找要插入的位置
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else//没找到+树中已有该值
			{
				return make_pair(cur, false);
			}
		}
			cur=new Node(key,value);

			if (cur->_key<parent->_key)
			{
				parent->_left = cur;
			}
			else {
				parent->_right = cur;
			}
			return make_pair(cur, true);

		
	}

4.删除:

		// 如果树中不存在key,返回false
		// 存在,删除后,返回true
		bool Erase(const K& key)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		if (nullptr == cur)
		{
			return false;
		}
		while (cur)
		{
			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;
						}
						
					}
					delete cur;
				}
				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;
						}
					}
					delete cur;
				}
				else {

					Node* minParent = cur;
					Node* minRight = cur->_right;
					while (minRight->_left)
					{
						parent = minRight;
						minRight = minRight->_left;
					}
					//cur = new Node(minRght->_key,minRight->_value);
					cur->_key = minRight->_key;
					cur->_val = minRight->_val;
					if (parent->_left == minRight)
					{
						parent->_left = minRight->_right;
					}
					else {
						parent->_right = minRight->_right;
					}
					
					delete minRight;
				}
				return true;
			}
			}
		return false;
	}

5.*测试:

一般我们将BST类实例化的时候,这个程序就有了功能。

例:维护一组英语单词数据<以英文作为键,中文作为属性>,可以提供英译汉,扩充字典,删除单词等功能。

void TestBSTree()
{
	KV::BSTree<string, string> dict;
	dict.InsertR("hello", "你好");
	dict.InsertR("baby", "婴儿");
	dict.InsertR("cat", "猫");
	dict.InsertR("dog", "狗");
	dict.InsertR("sort", "排序");
	// ...插入词库中所有单词
	string str;
	while (cin>>str)
	{
		KV::BSTreeNode<string, string>* ret = dict.FindR(str);
		if (ret == nullptr)
		{
			cout << "单词拼写错误,词库中没有这个单词:" <<str <<endl;
		}
		else
		{
			cout << str << "中文翻译:" << ret->_value << endl;
		}
	}
}

(四)源码分享

1.二叉搜索树非递归版本

C++ 模拟实现搜索二叉树 · 刘敬一如/数据结构_blog1 - Gitee.comhttps://gitee.com/liu-jingyiru/data-structure-blog1/blob/master/C++%20%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0%E6%90%9C%E7%B4%A2%E4%BA%8C%E5%8F%89%E6%A0%91

2.key-value模型

C++ 搜索二叉树KEY---VALUE模型 · 刘敬一如/数据结构_blog1 - Gitee.comhttps://gitee.com/liu-jingyiru/data-structure-blog1/blob/master/C++%20%E6%90%9C%E7%B4%A2%E4%BA%8C%E5%8F%89%E6%A0%91KEY---VALUE%E6%A8%A1%E5%9E%8B


                                      如有错误请多多指正,谢谢观看!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

刘敬_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值