【二叉树进阶】搜索二叉树的性能分析及其应用
前言
<font color = black>
1. 二叉搜索树的性能分析
插入和删除操作都必须先查找,所以查找效率就代表了二叉搜索树中各个操作的性能
<font color = black>那大家思考一下,搜索二叉树的查找的时间复杂度是多少? 那这个其实在不同情况下是不一样的: <font color = blue>如果二叉搜索树处于比较平衡的情况(接近完全二叉树),比如这样的 这种情况最坏的查找无非也就查找高度次(那如果结点数量为N,它的高度通常保持在logN的水平),所以这样它的时间复杂度就是
O(logN)
。 但是,避免不了出现这样的情况 二叉搜索树退化为单支树(或者接近单支),那这时查找的时间复杂度就应该是O(N)
。
所以,二叉搜索树的查找的时间复杂度
<font color = blue>最优情况下,二叉搜索树趋于平衡,其平均比较次数为:$log_2 N$,时间复杂度为O(logN) 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:$\frac{N}{2}$,时间按复杂度为O(N)
那么问题来了:
<font color = black>如果退化成单支树,二叉搜索树的性能就失去了。 那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的AVL树和红黑树就可以上 场了。 这个到时候讲到再说...
2. 二叉搜索树的应用
2.1 K模型
搜索二叉树的第一个应用是K模型,什么是K模型呢,介绍一下:
<font color = black>其实呢就是一个在不在的问题。 <font color = blue>K模型:K模型即只有key作为关键码,结构中只存储Key,关键码即为需要搜索的值。
比如:
<font color = black>给一个单词word,判断该单词是否拼写正确,具体方式如下: <font color = "#000066">以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误
那我们上一篇实现的搜索二叉树其实就是按照K模型搞的,所以这里就不举具体的例子了。
2.2 KV模型
<font color = blue>KV模型即每一个关键码key,都有与之对应的值Value,即结构中存储<Key, Value>的键值对。
该种方式在现实生活中非常常见:
<font color = black>比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对; 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
那接下来我们就来改造一下我们上一篇文章实现的搜索二叉树,改造成KV模型来简单解决一下上面提到的两个问题。
英汉互译
怎么改造呢,也很简单:
第一步:
<font color = black>我们可以把上一篇文章写的那个放到一个Key的命名空间,然后拷贝一份放到KV的命名空间里进行修改。
然后,大部分地方都不用变,把这几个地方改一下就行了
<font color = black> 其实就是增加一个模板参数,结点里面增加一个成员_val; 然后涉及到的模板的地方和相关的成员函数改一下就行了 首先插入的函数肯定得改一下 ps:代码比较长,就不全部放出来了,最后会给大家源码,大家不清楚的地方可以看
然后查找我们要简单改一下
<font color = black>因为现在是KV模型,完成英汉互译,不像之前那样查到了返回true,查不到返回false 应该是查到返回对应的结点,通过结点我们可以拿到对应的val,查不到返回个nullptr。
然后删除其实就不用改了,因为删除还是用key去找对应的结点删除就行了。 至于剩下的,大家有兴趣可以自己修改一下,我们这里就直接把剩下的其它一些接口删掉了。
然后我们来写个程序测试一下:
<font color = black> 运行看看 然后关于这里这个循环如何结束,我们上面提到可以输入Ctrl+Z,然后回车,这就是正常让它结束。 当然还可以输入Ctrl+c,直接就结束了,不过Ctrl+c是直接让进程终止。
统计次数
接下来我们再来搞一个,还是用KV模型:
<font color = black> 现在有一些水果,我们想统计每种水果出现的次数。
可以怎么搞呢? <font color = black>🆗,我们还是用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();
}
- 点赞
- 收藏
- 关注作者
评论(0)