二叉搜索树

二叉搜索树概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
例如:
在这里插入图片描述

二叉搜索树的实现

结点类

首先实现一个结点类,结点类中有3个成员变量:

template<class K>
struct BSTNode
{
	BSTNode* _left;//左指针
	BSTNode* _right;//右指针
	K _key; //结点值
	//构造函数
	BSTNode(const K& key)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
};

主要接口

template<class T>
class BSTree
{
	typedef BSTNode<T> Node;
public:
	BSTree()
		:_root(nullptr)
	{}
	//插入
    bool Insert(const K& key)//查找
    Node* Find(const T& key);
    //删除
    bool Erase(const T& key);
    //深拷贝
    BSTree(const BSTree<K>& t);
    //赋值
    BSTree<K>& operator=(BSTree<K> t)
    //析构
    void Dostory(Node* root)
    //中序遍历
	 void Inorder()
	private:
	Node* _root;//指向搜索树的根节点
};

迭代插入

在这里插入图片描述
当树为空的时候直接插入,树不为空时,例如:插入7,比5大就链接在5的右边,插入3,比5小,链接在5的左边。
动图演示:
在这里插入图片描述
具体实现:
在这里插入图片描述

     //迭代插入
	 bool Insert(const K& key)
	 {
		 //树为空直接new一个结点
		 if (_root == nullptr)
		 {
			 _root = new Node(key);
			 return true;
		 }
		 //树不为空,大的插到右边,小的插到左边
		 Node* cur = _root;
		 Node* parent = cur;//记录一下parent
		 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;
	 }

搜索树的插入接口的返回值是bool,插入成功返回true,插入失败返回false,我们就可以知道插入成功与否,并且比允许插入一样的值。

递归插入

递归实现的思路基本差不多,递归插入的子函数接受参数root时要用引用,这样才能链接起来。

	 //递归插入
	 bool InsertR(const K& key)
	 {
		 return _InsertR(_root, key);
	 }
	 //子函数建议私有
	 bool _InsertR(Node*& root, const K& key)
	 {
		 //空树直接插入
		 if (root == nullptr)
		 {
			 root = new Node(key);
			 return true;
		 }
		 //比key大就递归右边
		 if (root->_key < key)
		 {
			 return _InsertR(root->_right,key);
		 }
		 //比key小就递归左边
		 else if(root->_key > key)
		 {
			 return _InsertR(root->_left, key);
		 }
		 else
		 {
			 return false;
		 }
     }

在这里插入图片描述

用引用就不用找父亲结点了,此处体现了引用的价值。

中序遍历

搜索树的遍历建议使用中序遍历,我们来实现一下查看我们写的插入是否正确

	 //中序遍历
	 void Inorder()
	 {
		 _Inorder(_root);
		 cout << endl;
	 }
	 	 void _Inorder(Node* root)
	 {
		 //树为空直接返回
		 if (root == nullptr)
		 {
			 return;
		 }
		 //先递归左,打印值,再递归右
		 _Inorder(root->_left);
		 cout << root->_key << " ";
		 _Inorder(root->_right);
	 }

递归示意图:
在这里插入图片描述

在这里插入图片描述
此时插入的递归和非递归已完成。

迭代查找

查找就简单多了,比根节点大就到右边,小就到左边

	Node* Find(const T& 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 NULL;
	}

递归查找

	 //递归查找
	 Node* FindR(const K& key)
	 {
		 return _FindR(_root, key);
	 }
	  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;
		 }
	 }

迭代删除

删除的思路;
在这里插入图片描述
删除左为空的分析
在这里插入图片描述
删除右为空的细节根删除左是一样的,要画图分析
在探讨一波删除左右都不为空的
在这里插入图片描述
动图演示;
在这里插入图片描述
具体代码实现:

	bool Erase(const T& key)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
		    //先开始找到要删除的结点
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//开始删除
				//有1个孩子的
				if (cur->_left == nullptr)
				{
				     //没有左子树,让它的右做跟结点
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_right == cur)
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}
						
					}
					delete cur;
					return true;
				}
				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;
					    }					   
				    }
				   delete cur;
				   return true;
				}
				else
				{
					//有2个孩子的,替换法删除,找右子树中最小的
					Node* minRight = cur->_right;
					Node* minParent = cur;
					while (minRight->_left)
					{
						minParent = minRight;//记录minRight的父亲
						minRight = minRight->_left;//往左边找
					}							
					//保存key值
					cur->_key = minRight->_key;
					//判断待删结点是父亲的做还是右
					if (minParent->_left == minRight)
					{
						minParent->_left = minRight->_right;
					}
					else
					{
						minParent->_right = minRight->_right;
					}
					delete minRight;					
				}
				return true;
			}
		}
		return false;
	}

递归删除

递归删除的思路:
在这里插入图片描述
具体代码实现:

	 //递归删除
	 bool EraseR(const K& key)
	 {
		 return _EraseR(_root, key);
	 }
	 bool _EraseR(Node*& root, const K& key)
	 {
		 if (root == nullptr)
		 {
			 return false;
		 }
		 if (root->_key < key)
		 {
			 return _EraseR(root->_right, key);
		 }
		 else if (root->_key > key)
		 {
			 return _EraseR(root->_left, key);
		 }
		 else
		 {
			 //找到了开始删除
			 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;
				 }
				 //先保存minRight的值
				 K min = minRight->_key;
				 //再次调用自己
				 _EraseR(root->_right,min);
				 //把root的值换位min的值
				 root->_key = min;
             }
			 return true;
		 }		 
	 }

测试一下删除,测试的时候要测试把树删完

在这里插入图片描述
删除完成。

拷贝构造

先拷贝根节点的值,在拷贝左子树,然后右子树即可

	 //拷贝构造
	 BSTree(const BSTree<K>& t)
	 {
		 _root = _Copy(t._root);
	 }
	 	 Node* _Copy(Node* root)
	 {
		 if (root == nullptr)
		 {
			 return nullptr;
		 }
		 
		 Node* copynode = new Node(root->_key);
		 //先拷贝左,在拷贝右
		 copynode->_left = _Copy(root->_left);
		 copynode->_right = _Copy(root->_right);

		 return copynode;
	 }

赋值

赋值跟之前的vector,list一样,用现代写法,传值然后交换即可

	 //赋值,调用拷贝构造,传值然后交换
	 BSTree<K>& operator=(BSTree<K> t)
	 {
		 swap(_root, t._root);
		 return *this;
	 }

在这里插入图片描述

析构

释放结点,按二叉树的后序来释放,最后将指向二叉树的指针置空即可。

	 //析构
	 ~BSTree()
	 {
		 _Destory(_root);
		 _root = nullptr;
	 }
     void _Destory(Node* root)
	 {
		 if (root == nullptr)
		 {
			 return;
		 }
		 //先释放左,在释放右
		 _Destory(root->_left);
		 _Destory(root->_right);
		 delete root;
	 }

搜索树的应用

K模型

K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值

比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

KV模型

KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。

每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生
活中非常常见:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对
<单词,中文含义>为键值对构造二叉搜索树,注意:二叉搜索树需要比较,键值对比较时只比较Key,查询英文单词时,只需给出英文单词,就可快速找到与其对应的key。

测试代码:

void test1()
{
	kv::BSTree<string,string> dict;
	dict.InsertR("basketball", "篮球");
	dict.InsertR("sun", "太阳");
	dict.InsertR("insert", "插入");
	dict.InsertR("girl", "女孩");

	string str;
	while (cin>>str)
	{
		kv::BSTNode<string, string>* ret = dict.FindR(str);
		if (ret == nullptr)
		{
			cout << "单词拼写错误,词库中没有这个单词" << str << endl;
		}
		else
		{
			cout << "中文翻译:" << ret->_value << endl;
		}
	}
}

效果如下:
在这里插入图片描述
只需录入单词就可以查到中文意思,这就是KV模型的应用。
KV模型完整代码
完整代码

在这里插入图片描述
搜索树变成单支效率就不行了,后序右AVL树和红黑树来解决问题。
在这里插入图片描述

  • 41
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 45
    评论
评论 45
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_End丶断弦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值