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

举报
YIN_尹 发表于 2023/12/22 17:17:29 2023/12/22
【摘要】 @[TOC]前言<font color = black>上一篇文章我们学习了搜索二叉树的实现,这篇文章我们来对搜索二叉树进行一个性能分析,并来讲解一下它的应用。1. 二叉搜索树的性能分析插入和删除操作都必须先查找,所以查找效率就代表了二叉搜索树中各个操作的性能<font color = black>那大家思考一下,搜索二叉树的查找的时间复杂度是多少? 那这个其实在不同情况下是不一样的: <f...

@[TOC]

前言

<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();
}

在这里插入图片描述

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。