C++:搜索二叉树模拟以及二叉树OJ

 "Oh hey New Wrold!'


(1)搜索二叉树:

①什么是搜索二叉树:

搜索二叉树,又称为二叉排序树。具有二叉树性质。
 

如若它的左子树不为空,那么左子树所有的节点小于根节点。

如若它的右子树不为空,那么右子树所有的节点大于根节点。

②搜索二叉树查找

 


(2)搜索二叉树的模拟实现:

①结构:

namespace dy
{
	//树的节点
	template<class K>
	struct TreeNode
	{
		TreeNode<K>* _left;
		TreeNode<K>* _right;
		K _key;

		TreeNode(const K& key)
			:_left(nullptr)
			,_right(nullptr)
			,_key(key)
		{}
	};
   
   //搜索二叉树
	template<class K>
	class BSTree
	{
		typedef TreeNode<K> TNode;
	public:
		BSTree()
			:_root(nullptr)
		{}

	private:
		TNode* _root;  //根节点
 	};
}

 


 


 ②树的插入:

非递归版本: 

	bool Insert(const K& key)
		{
			if (_root == nullptr)
			{
				_root = new TNode(key);
			}

			//cur记录插入的位置
			TNode* cur = _root;
			//parent为父节点 记录要连接的位置
			TNode* parent = nullptr;

			while (cur)
			{
				if (key > cur->_key)
				{
					parent = cur;
					cur = cur->_right;
			   }
				else if (key < cur->_key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else  //相等 既 已经插入过key值了
				{
					return false;
				}
			}
			
			//此时cur 为空
			cur = new TNode(key);

			//链接
			if (key > parent->_key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			return true;
		}

递归版本:

递归版本的思想极其巧妙。 

 

          //非递归
		//和中序 一样 用到递归 就需要借用子函数去执行
		bool InsertR(const K& key)
		{
			if (_root == nullptr)
			{
				_root = new TNode(key);
				return true;
			}
			else
			{
				return _InsertR(_root, key);
			}
		}

       bool _InsertR(TNode*& root, const K& key)
		{
			//root为空的时候 创建并插入节点
			if (root == nullptr)
			{
				//root 实质上是  上一个parent节点的NULL
				root = new TNode(key);
				return true;
			}

			if (key > root->_key)
			{
				//每一种情况进行返回
				return _InsertR(root->_right, key);
			}
			else if (key < root->_key)
			{
				//每一种情况进行返回
				return _InsertR(root->_left, key);
			}

			return false;
		}

 


 

 ③中序打印:

 我们先来插入一些数值,试试。 需要注意的规律,搜索二叉树走中序,也就会成为顺序。

private:
		//当然我们也需要 让子函数 成为私有 仅供成员内部访问
		void InOrder(TNode* root)
		{
			if (root == nullptr)
				return;

			//递归先走左 树  再走右树
			InOrder(root->_left);
			cout << root->_key << endl;
			InOrder(root->_right);
		}

void InOrder()
		{
			if (_root == nullptr)
				return;
			 else 
				_InOrder(_root);
		}

 

 那么我们的插入也就没有问题了。

 


 

④查找:

找到树的节点返回,否则返回nullptr.

非递归版本:

	//查找
		TNode* Find(const K& key)
		{
			TNode* cur = _root;
			while (cur)
			{
				if (key > _root->_key)
				{
					cur = cur->_right;
				}
				else if (key < _root->_key)
				{
					cur = cur->_left;
				}
			}

			return cur;
		}

递归版本:

	//查找
		TNode* _FindR(TNode* root, const K& key)
		{
			if (key > root->_key)
			{
				return _FindR(root->_right, key);
			}
			else if (key < root->_key)
			{
				return _FindR(root->_left, key);
			}

			return root;
		}

 查找的思想就很简单了,弄懂 上面的插入 这里的代码理解起来不是很难。

 


 

⑤删除:

搜索二叉树 的删除 相较于上面功能 实现起来较为复杂。

 

非递归:

删除单一节点:

 

 删除父节点:

 

上面的是一种传统写法: 

我们对于MinRight的删除 可以采用 复用的方式。

 

我们删除的功能似乎完成得很好。 

但如何有效地检验自己代码是否合格,最直接办法就是,删完整个树。

 此时 代码跑死了。        

测试:

我们很快反应 过来,就是删除到最后一个节点的时候会出问题。

//删除
		bool Erase(const K& key)
		{
			//让cur 作为被删除的点
			TNode* cur = _root;
			TNode* parent =nullptr;
			while (cur)
			{
				if (key > cur->_key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (key < cur->_key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					//开始删除
					//其实第一种和第二种 情况 可以合并 因为都是让 
					//cur的父节点指向 自己子节点(nullptr这种情况可以勉强认为是)
					//需要被认领的是 右子节点
					if (cur->_left == nullptr)
					{
						//root为最后一个
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							//cur在parent的那边
							if (parent->_left == cur)
							{
								//在左边就用左边去  链接 孩子
								parent->_left = cur->_right;
							}
							else
							{
								//在右边 就用右支 认领 孩子
								parent->_right = cur->_right;
							}
						}
						//记到删除
						delete cur;
				    }
					else if (cur->_right == nullptr) //cur的左节点需要被认领
					{
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							//cur 在 父节点的 左边
							if (parent->_left == cur)
							{
								//左边去认领
								parent->_left = cur->_left;
							}
							else
							{
								//否则就 用右边去认领
								parent->_right = cur->_left;
							}
						}
						delete cur;
					}
					else
					{
						/*删除父亲节点
						替换法 首先找到能替换父节点的值 这里我们统一找大的那个值
						也就是 右子树最小值
						去走cur 的右支*/
						//TNode* MinRight = cur->_right; 
						右子树最小值的父节点
						//TNode* MinParent = cur;
						//while (MinRight->_left)  //注意这类不能用MinRight 作为判断条件否则 会走到空
						//{
						//	MinParent = MinRight;
						//	MinRight = MinRight->_left;
						//}

						此时MinRight为要替换的节点
						并保存值 
						//K Min = MinRight->_key;

						传统写法
						删除更换的节点
						//if (MinParent->_left == MinRight)  
						//{
						//	MinParent->_left = MinRight->_right;
						//}
						//else //排除 MinRight 不在cur 的左支
						//{
						//	MinParent->_right = MinRight->_right;
						//}
						//cur->_key = Min;
						//delete MinRight;

						//复用
						TNode* MinRight = cur->_right;
						while (MinRight->_left)
						{
							MinRight = MinRight->_left;
						}

						//找到MinRight
						K min = MinRight->_key;

						//删除
						this->Erase(min);

						cur->_key = min;
					}
					return true;
				}
			}
			return false;
		}

 


 

递归版本:

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

bool _EraseR(TNode*& root, const K& key)
		{
		    //Erase 递归删除 仍然可以 借用引用
			if (key > root->_key)
			{
				return _EraseR(root->_right, key);
			}
			else if(key < root->_key)
			{
				return _EraseR(root->_left, key);
			}
			else
			{
			    //相等
				//这里还是两者情况
				if (root->_left == nullptr)
				{
					//先保存到这个root点
					TNode* del = root;

					//直接让root 点 链接到下一个 跳过了要删除的root
					root = root->_right;

					delete del;
				}
				else if (root->_right == nullptr)\
				{
					TNode* del = root;
					root = root->_left;

					delete del;
				}
				else
				{
					//删除双节点
					//这里就不能 用替换法 删除
					TNode* MinRight = root->_right;
					while (MinRight->_left)
					{
						MinRight = MinRight->_left;
					}

					//找到准备替换的值
					K min = MinRight->_key;

					//调用自己  去删除 min 的节点
					this->_EraseR(root->_right, min);

					//注意这里 的root 是没有动的 一直用的是引用!!
					root->_key = min;
				}
				return true;
			}

			return false;
		}

 整个搜索二叉树,就只有删除难度稍大。


⑥拷贝、赋值、析构成员函数:

很显然,如果仅凭 编译器提供的拷贝、赋值 只是完成值拷贝。

所以需要我们去手段写。 


 

	//拷贝构造
		//t2(t1)
		BSTree(const BSTree<K>& t)
		{
			 //我们需要去递归 new和t一样的 节点
			_root = _Copy(t._root);
		}

TNode* _Copy(TNode* root)
		{
			if (root == nullptr)
				return nullptr;

			//每个拷贝创建的节点
			TNode* copyroot = new TNode(root->_key);

			//去构建他的左
			copyroot->_left = _Copy(root->_left);
			//右
			copyroot->_right = _Copy(root->_right);

			return copyroot;
		}

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


		//析构
		~BSTree()
		{
			//这里交给 这个子函数去删除节点
			_Destory(_root);
			_root = nullptr;
		}
		void _Destory(TNode* root)
		{
			if (root == nullptr)
				return;

			_Destory(root->_left);
			_Destory(root->_right);

			delete root;
		}


(3)搜索二叉树实际应用:

①性能分析:

可能对搜索二叉树的第一印象,就是查找数据的时间复杂度为高度次O(logN);

但在极端情况下,并不是。而是O(N);

只有在完全二叉树或者满二叉树的条件下,可以达到O(logN) 

 

②适用场景;

搜索二叉树 顾名思义,用于查找比较方便。在这基础上 延伸出来 KV树;

我们可以用来查阅什么东西,比如说字典:

template<class K,class V>
	struct TreeNode
	{
		TreeNode<K,V>* _left;
		TreeNode<K,V>* _right;
		K _key;
		V _val;

		TreeNode(const K& key,const V& val)
			:_left(nullptr)
			, _right(nullptr)
			, _key(key)
			,_val(val)
		{}
	};

	template<class K,class V>
	class BSTree
	{
		typedef TreeNode<K, V> TNode;
	public:
		BSTree()
			:_root(nullptr)
		{}

		bool _InsertR(TNode*& root, const K& key,const V& val)
		{
			//root为空的时候 创建并插入节点
			if (root == nullptr)
			{
				//root 实质上是  上一个parent节点的NULL
				root = new TNode(key,val);
				return true;
			}

			if (key > root->_key)
			{
				//每一种情况进行返回
				return _InsertR(root->_right, key,val);
			}
			else if (key < root->_key)
			{
				//每一种情况进行返回
				return _InsertR(root->_left, key,val);
			}

			return false;
		}

		bool InsertR(const K& key,const V& val)
		{
			if (_root == nullptr)
			{
				_root = new TNode(key,val);
				return true;
			}
			else
			{
				return _InsertR(_root, key,val);
			}
		}

		void _InOrder(TNode* root)
		{
			if (root == nullptr)
				return;

			//递归先走左 树  再走右树
			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}

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

		//查找
		TNode* _FindR(TNode* root, const K& key)
		{
			if (key > root->_key)
			{
				return _FindR(root->_right, key);
			}
			else if (key < root->_key)
			{
				return _FindR(root->_left, key);
			}
			return root;
		}

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

	private:
		TNode* _root;
	};

 我们对节点内的 模板参数设置增加一个,用于存另外的值。

 

统计个数: 

 


搜索二叉树的讲解就到这里 了。

感谢你的阅读。

祝你好运~ 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值