二叉搜索树

目录

二叉搜索树概念

二叉树搜索树的模拟实现

1.插入 Insert

2.Erase 删除结点(难点)

3.InOder (中序遍历)

4.Find 

递归实现方式

完整代码

总结


二叉搜索树概念

其又称二叉排序树、二叉查找树。

满足以下性质:
非空左子树的key值小于根结点的key值;

非空右子树的key值大于根结点的key值;

左、右子树都是二叉搜索树。

二叉树搜索树的模拟实现

1.插入 Insert

 在树中,寻找适合key插入的位置,找到插入返回true ,已有重复key返回false.

思路:

比较key 和根结点的_key   如果key大于_key 往根的右子树去找   

如果key小于_key 往根的左子树去找

如果树中不存在已有的key 则必定走到空结点 ,链接上key的值即可

如果树中存在key 结束查找 返回false 

画图辅助理解

在树中,插入key为五的结点:

定义一个cur临时结点

1--比较cur->_key 与key(下面简称比较)   key小于cur->_key   往左子树中查找 cur=cur->left

2--比较   key>cur->_key   往右子树查找   cur=cur->right

3--比较    key<cur->_key  往左子树查找  cur=cur->left

4--比较     key>cur->_key    往右子树查找   cur=cur->right  

5--cur为空  找到可以链接的位置 链接上结点

 以下有俩点需要注意:

1.如果树为空 ,则需要先创建树

2.链接结点,需要保存cur的父亲

下面代码演示

		bool Insert(const K& key)
		{
			if (_root == nullptr)
			{
				_root = new Node(key);
				return true;
			}
			
			//找到适合插入的位置
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur) 
			{
				parent = cur;  //更新parent
				if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else if (cur->_key < key) //更新parent
				{
					cur = cur->_right;
				}
				else
					return false;
			}

			//准备插入  插入点一定是空结点
			cur = new Node(key);

			//判断插入父亲的左还是右
			if (parent->_key > key) parent->_left = cur;
			else parent->_right = cur;

			//插入成功
			return true; 

		}

2.Erase 删除结点(难点)

关于二叉搜索树的删除相对不容易,需要考虑多种情况,下面就来详细解释。

上图列举四种具有代表性的删除结点

分别是:

key结点的左和右子树都为空     key的左子树为空  key的右子树为空  key的左和右子树都不为空

对于左子树右子树都为空 我们可以归于左子树或者右子树为空的一种中

下文 只需要对 2  3  4三种方式进行剖析

这三种方式都必须先找到cur

找cur的方式与inser相同 ,在此不做介绍

1)cur的左为空

--1--判断cur是否为根结点   是则将cur->left赋值给_root 

--2--判断cur是父亲的左还是右 ,链接cur->right

2)cur的右为空

该方式与1)类似 

需要判断cur是否为root结点 

不为root则需要判断是父亲的左还是右结点

在此不做过多的介绍

3)cur的左和右子树都不为空

如果我们简单的删除cur ,则破坏搜索二叉树,因此我们引入替换法

在cur的子树中,找到一个合适的值替换,使树仍为搜索二叉树

找替换结点的方式:
左子树中,找最大    

右子树中,找最小

如果我们要删除key为3的结点 因为cur的结点有左子树和右子树 所以需要找替换 

我们找cur右子树的最小值 即右子树最左边的元素  4  

交换 3 和 4的值  然后删除 rightmin的结点  

要删除rightmin结点 需要注意该结点的右子树需要被链接到cur的父亲上  

代码剖析

		bool Erase(const K& key)
		{
			//1.k的左为空
			//2.k的右为空
			//3.k左右都存在,替换删除

			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				parent = cur; //更新父亲
				if (cur->_key < key) cur = cur->_right;
				else if (cur->_key > key) cur = cur->_left;

				//找到了
				else
				{
					//1.左为空
					if (cur->_left == nullptr)
					{

						//删除点为根
						if (cur == _root) _root = cur->_right;

						//判断是父亲的左、右链接
						if (parent->_right == cur)  //右链接
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}

					}

					//2.右为空
					else if (cur->_right == nullptr)
					{
						//删除点为根
						if (cur == _root) _root = cur->_left;

						//判断是父亲的左、右链接
						if (parent->_right == cur)  //右链接
						{
							parent->_right = cur->_left;
						}
						else
						{
							parent->_left = cur->_left;
						}
					}

					//3.替换
					else
					{
						//找右数的最小 右子树的最左边元素
						Node* pminright = cur;
						Node* minright = cur->_right;
						
						//找到左树空就找到了
						while (minright->_left)
						{
							pminright = minright;  //保存父亲结点
							minright = minright->_right;	
						}

						cur->_key = minright->_key;  //交换

						//判断是父亲的左子树还是右子树要被删除
						if (pminright->_left ==minright )
							pminright->_left = minright->_right;
						else 
							pminright->_right= minright->_right;
					}
				}
			}
			delete(cur);
			return true;
		}

 

3.InOder (中序遍历)

规则:左子树 ——根结点——右子树

 

遍历顺序  1  3  4  6  7  8  10  13  14 

搜索二叉树的中序遍历可以将key按照大小遍历出

对于一个类中,我们调用InOder函数 是无法传递私有变量_root 

因为写一个子函数提供遍历

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

			_InOrder(root->_left);
			cout << root->_key << ":" << root->_val << endl;
			_InOrder(root->_right);

		}

4.Find 

查找树中是否存在值为key的函数,找到返回结点指针,找不到返回空

思路是从根结点开始 ,如果key 小于cur->_key 那么往左子树查找,大于则往右子树查找

		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--cur的链接,不需要保存父亲结点 引用cur的指针  

完整代码
 

展示key_value模型应用的代码

#include<iostream>
#include<string>
using namespace std;


namespace key_val
{
	template<class K, class V> //key和value
	//node
	struct BSTreeNode
	{
		BSTreeNode<K, V>* _left;
		BSTreeNode<K, V>* _right;
		K _key;
		V _val;

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

	template<class K,class V>
	class BSTree
	{
		typedef BSTreeNode<K, V> Node;
	public:
		bool Insert(const K& key, const V& value)
		{
			if (_root == nullptr)
			{
				_root = new Node(key, value);
				return true;
			}
			
			//找到适合插入的位置
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur) 
			{
				parent = cur;  //更新parent
				if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else if (cur->_key < key) //更新parent
				{
					cur = cur->_right;
				}
				else
					return false;
			}

			//准备插入  插入点一定是空结点
			cur = new Node(key, value);

			//判断插入父亲的左还是右
			if (parent->_key > key) parent->_left = cur;
			else parent->_right = cur;

			//插入成功
			return true; 

		}
		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; //没找到,返回空指针
		}


		bool Erase(const K& key)
		{
			//1.k的左为空
			//2.k的右为空
			//3.k左右都存在,替换删除

			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				parent = cur; //更新父亲
				if (cur->_key < key) cur = cur->_right;
				else if (cur->_key > key) cur = cur->_left;

				//找到了
				else
				{
					//1.左为空
					if (cur->_left == nullptr)
					{

						//删除点为根
						if (cur == _root) _root = cur->_right;

						//判断是父亲的左、右链接
						if (parent->_right == cur)  //右链接
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}

					}

					//2.右为空
					else if (cur->_right == nullptr)
					{
						//删除点为根
						if (cur == _root) _root = cur->_left;

						//判断是父亲的左、右链接
						if (parent->_right == cur)  //右链接
						{
							parent->_right = cur->_left;
						}
						else
						{
							parent->_left = cur->_left;
						}
					}

					//3.替换
					else
					{
						//找右数的最小 右子树的最左边元素
						Node* pminright = cur;
						Node* minright = cur->_right;
						
						//找到左树空就找到了
						while (minright->_left)
						{
							pminright = minright;  //保存父亲结点
							minright = minright->_right;	
						}

						cur->_key = minright->_key;  //交换

						//判断是父亲的左子树还是右子树要被删除
						if (pminright->_left ==minright )
							pminright->_left = minright->_right;
						else 
							pminright->_right= minright->_right;
					}
				}
			}
			delete(cur);
			return true;
		}

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

			_InOrder(root->_left);
			cout << root->_key << ":" << root->_val << endl;
			_InOrder(root->_right);

		}

	private:
		Node* _root = nullptr;
	
	
	};

总结

本文重点学习搜索二叉树的插入和删除,它具有快速查找的功能,在查找具有O(logN)-O(N)的效率,关于搜索二叉树的实现,有较多的细节,也要仔细甄别。

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 21
    评论
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

深度搜索

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

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

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

打赏作者

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

抵扣说明:

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

余额充值