【二叉树进阶】搜索二叉树的性能分析及其应用

前言

上一篇文章我们学习了搜索二叉树的实现,这篇文章我们来对搜索二叉树进行一个性能分析,并来讲解一下它的应用。

1. 二叉搜索树的性能分析

插入和删除操作都必须先查找,所以查找效率就代表了二叉搜索树中各个操作的性能

那大家思考一下,搜索二叉树的查找的时间复杂度是多少?
那这个其实在不同情况下是不一样的:
如果二叉搜索树处于比较平衡的情况(接近完全二叉树),比如这样的
在这里插入图片描述
这种情况最坏的查找无非也就查找高度次(那如果结点数量为N,它的高度通常保持在logN的水平),所以这样它的时间复杂度就是O(logN)
但是,避免不了出现这样的情况
在这里插入图片描述
二叉搜索树退化为单支树(或者接近单支),那这时查找的时间复杂度就应该是O(N)

所以,二叉搜索树的查找的时间复杂度

最优情况下,二叉搜索树趋于平衡,其平均比较次数为: l o g 2 N log_2 N log2N,时间复杂度为O(logN)
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N,时间按复杂度为O(N)

那么问题来了:

如果退化成单支树,二叉搜索树的性能就失去了。
那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的AVL树和红黑树就可以上
场了。
这个到时候讲到再说…

2. 二叉搜索树的应用

2.1 K模型

搜索二叉树的第一个应用是K模型,什么是K模型呢,介绍一下:

其实呢就是一个在不在的问题。
K模型:K模型即只有key作为关键码,结构中只存储Key,关键码即为需要搜索的值。

比如:

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

那我们上一篇实现的搜索二叉树其实就是按照K模型搞的,所以这里就不举具体的例子了。

2.2 KV模型

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

该种方式在现实生活中非常常见:

比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

那接下来我们就来改造一下我们上一篇文章实现的搜索二叉树,改造成KV模型来简单解决一下上面提到的两个问题。

英汉互译

怎么改造呢,也很简单:

第一步:

我们可以把上一篇文章写的那个放到一个Key的命名空间,然后拷贝一份放到KV的命名空间里进行修改。
在这里插入图片描述

然后,大部分地方都不用变,把这几个地方改一下就行了

在这里插入图片描述
其实就是增加一个模板参数,结点里面增加一个成员_val;
然后涉及到的模板的地方和相关的成员函数改一下就行了

首先插入的函数肯定得改一下在这里插入图片描述
ps:代码比较长,就不全部放出来了,最后会给大家源码,大家不清楚的地方可以看

然后查找我们要简单改一下

因为现在是KV模型,完成英汉互译,不像之前那样查到了返回true,查不到返回false
应该是查到返回对应的结点,通过结点我们可以拿到对应的val,查不到返回个nullptr。

在这里插入图片描述

然后删除其实就不用改了,因为删除还是用key去找对应的结点删除就行了。
至于剩下的,大家有兴趣可以自己修改一下,我们这里就直接把剩下的其它一些接口删掉了。

然后我们来写个程序测试一下:

在这里插入图片描述
运行看看
在这里插入图片描述
然后关于这里这个循环如何结束,我们上面提到可以输入Ctrl+Z,然后回车,这就是正常让它结束。
当然还可以输入Ctrl+c,直接就结束了,不过Ctrl+c是直接让进程终止。
在这里插入图片描述

统计次数

接下来我们再来搞一个,还是用KV模型:

在这里插入图片描述
现在有一些水果,我们想统计每种水果出现的次数。

可以怎么搞呢?

🆗,我们还是用KV来存储键值对,key是水果的名字,val是出现的次数。
在这里插入图片描述
然后:
遍历存储水果的string数组,判断每一个水果在不在countTree里面,不在,就添加进去,把val置成1,在的话,就只让val++就行了。
这样遍历一遍,就统计出次数了。
在这里插入图片描述
运行一下
在这里插入图片描述
就统计出来了。

3. 源码展示

3.1 KV结构改造

namespace kv
{
	template <class K, class V>
	struct BSTreeNode
	{
		BSTreeNode(const K& key, const V& val)
			:_left(nullptr)
			, _right(nullptr)
			, _key(key)
			, _val(val)
		{}

		BSTreeNode<K,V>* _left;
		BSTreeNode<K,V>* _right;
		K _key;
		V _val;
	};

	template <class K, class V>
	class BSTree
	{
		typedef BSTreeNode<K,V> Node;
	public:
		bool Insert(const K& key, const V& val)
		{
			if (_root == nullptr)
			{
				_root = new Node(key, val);
				return true;
			}
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (key > cur->_key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					return false;
				}
			}
			//走到这里cur为空,就是key应该插入的位置
			cur = new Node(key, val);
			//链接
			if (key < parent->_key)
				parent->_left = cur;
			if (key > parent->_key)
				parent->_right = cur;
			return true;
		}
		Node* Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_key)
				{
					cur = cur->_left;
				}
				else if (key > cur->_key)
				{
					cur = cur->_right;
				}
				else
				{
					return cur;
				}
			}
			return nullptr;
		}
		bool Erase(const K& key)
		{
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (key > cur->_key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					//key == cur->_key就是找到了,进行删除
					//1.左为空
					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;
						cur = nullptr;
					}
					//2.右为空
					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;
						cur = nullptr;
					}
					else//左右都不为空
					{
						//这里选择右树的最小结点(最左边)替换
						Node* pminRight = cur;
						Node* minRight = cur->_left;
						while (minRight->_left)
						{
							pminRight = minRight;
							minRight = minRight->_left;
						}
						cur->_key = minRight->_key;
						//删除替换结点
						if (pminRight->_left = minRight)
						{
							pminRight->_left = minRight->_right;
						}
						else
						{
							pminRight->_right = minRight->_right;
						}
						delete minRight;
						minRight = nullptr;
					}
					return true;
				}
			}
			//找不到直接返回false
			return false;
		}
		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;
			_InOrder(root->_left);
			cout << root->_key << root->_val << " ";
			_InOrder(root->_right);
		}
	private:
		Node* _root = nullptr;
	};
}

3.2 测试

void TestBSTree5()
{
	kv::BSTree<string, string> dict;
	dict.Insert("sort", "排序");
	dict.Insert("left", "左边");
	dict.Insert("right", "右边");
	dict.Insert("string", "字符串");
	dict.Insert("insert", "插入");
	dict.Insert("erase", "删除");

	string str;
	while (cin >> str)
	{
		auto ret = dict.Find(str);
		if (ret)
		{
			cout << ":" << ret->_val << endl;
		}
		else
		{
			cout << "无此单词" << endl;
		}
	}
}
void TestBSTree6()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };

	kv::BSTree<string, int> countTree;
	for (auto fruit : arr)
	{
		//kv::BSTreeNode<string, int>* ret = conutTree.Find(e);
		auto ret = countTree.Find(fruit);
		if (ret == nullptr)
		{
			countTree.Insert(fruit, 1);
		}
		else
		{
			++ret->_val;
		}
	}
	countTree.InOrder();
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

YIN_尹

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

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

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

打赏作者

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

抵扣说明:

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

余额充值