【数据结构】什么是二叉搜索(排序)树?

举报
修修修也 发表于 2024/10/25 15:49:27 2024/10/25
【摘要】 ​🦄个人主页:修修修也 🎏所属专栏:数据 结 构 ⚙️操作环境:Visual Studio 2022​编辑目录📌 二叉搜索(排序) 树 的概念 📌 二叉搜索(排序) 树 的操作 🎏 二叉搜索 树 的 查 找 🎏 二叉搜索 树 的插入 🎏 二叉搜索 树 的 删 除 📌 二叉搜索(排序) 树 的 实现 📌 二叉搜索(排序) 树 的 应 用 🎏 K模型 🎏 KV模型 📌 二...

​🦄个人主:修修修也

🎏所属专栏:数据

⚙️操作:Visual Studio 2022

​编辑


📌 二叉搜索(排序) 的概念

📌 二叉搜索(排序) 的操作

🎏 二叉搜索

🎏 二叉搜索 的插入

🎏 二叉搜索

📌 二叉搜索(排序) 实现

📌 二叉搜索(排序)

🎏 K模型

🎏 KV模型

📌 二叉搜索(排序) 的性能分析

结语


📌二叉搜索(排序)的概念

        我们今天要介绍的树是一种非常适合于搜索/排序的树, 当然二叉搜索(排序)树的前提是它是一颗树,并且是一颗二叉树。因此对于树以及二叉树的定义还有不太了解的朋友建议先移步这两篇博客补充一下数据结构树的相关前置知识:

【数据 构】什么是 ? https://blog.csdn.net/weixin_72357342/article/details/134973723?spm=1001.2014.3001.5502

【数据 构】什么是二叉 ? https://blog.csdn.net/weixin_72357342/article/details/135162895?sharetype=blogdetail&sharerId=135162895&sharerefer=PC&sharesource=weixin_72357342&spm=1011.2480.3001.8118

        二叉搜索(BST, Binary Search Tree) 也称二叉排序树或二叉查找树。它或是,或是具有下列性的二叉:

若它的左子,左子上所有点的均小于它的根点的

若它的右子,右子上所有点的均大于它的根点的

它的左右子也分别为二叉搜索

        下图罗列了部分属于/不属于二叉搜索树的二叉树以供参考:

​编辑

        对于二叉搜索树而言,其中序遍果恰好是中所有的有序排列,因此特性也称其为二叉排序。构造一棵二叉排序树的目的,其实并不是为了排序,而是为了提高找和插入除关字的速度。不管怎么说,在一个有序数据集上的查找,速度总是要快于无序的数据集的,而二叉排序树这种非线性的结构,同样有利于插入和除操作的实现


📌二叉搜索(排序)的操作

        以如下二叉搜索树为例, 分别剖析二叉搜索,插入和除操作:

​编辑

int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };

🎏二叉搜索

        二叉搜索树的查找过程如下:

1. 从根点开始比较查, 如果比根,往右子树继续查; 如果比根,往左子树继续查;

2. 最多找高度次, 即找到叶子, 如果没找到, 则该值不存在于此中。

​编辑

🎏二叉搜索的插入

        二叉搜索树的插入过程如下:

1. 树为,直接新增,赋值给点指

2. ,按二叉搜索质查找插入位置, 在找到空的位置插入新增, 如果找到该值已存在, 返回找失(二叉搜索不允插入重复)

​编辑

🎏二叉搜索

        查找元素是否在二叉搜索中,如果不存在,则返回,如果存在,则待删除结点可能存在以下四种情况:

1. 点无孩子

2. 点只有左孩子(不管右孩子)

3. 点只有右孩子(不管左孩子)

4. 点左,右孩子点都有

        在实际删除操作中,可以将1和2 / 3合并起来,因为我们的逻辑是哪边有孩子就管理,没有孩子就可以不管,因此无孩子结点既可以和不管右孩子结点情况合并又可以和不管左孩子情况合并,合并后实际的删除情况及过程如下三种:

1. 该结点且使被点的双亲结点指向被点的左孩子--直接

2. 该结点且使被点的双亲结点指向被点的右孩子--直接

3. 在它的右子找中序下的第一个(关键码最小),用它的到被点中,再来该结点的问题--替

​编辑


📌二叉搜索(排序)实现

        由于本文偏向理论概念,篇幅有限,因此关于使用C++具体实现二叉搜索(排序)树的详细过程我放在下面这篇文章中了,有需要的朋友可以移步这篇博客,里面有非常详细的使用C++实现二叉搜索(排序)树的详解:【C++】模 拟实现 二叉搜索 (排序) https://blog.csdn.net/weixin_72357342/article/details/142413312?spm=1001.2014.3001.5502         文章目录概览:

​编辑


📌二叉搜索(排序)

🎏K模型

        K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
        比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:

• 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树

• 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。


🎏KV模型

        KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:

• 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;

• 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

        二叉搜索树改key_value模型及其应用代码:

namespace key_value

{

    template<class K,class V>

    struct BSTreeNode

    {

        BSTreeNode<K,V>* _left;

        BSTreeNode<K,V>* _right;

        K _key;

        V _value;

        BSTreeNode(const K& key = K(), const V& value = V())

            :_left(nullptr)

            , _right(nullptr)

            , _key(key)

            , _value(value)

        {}

    };




    template<class K, class V>

    class BSTree

    {

        typedef BSTreeNode<K,V> Node;

    public:

        BSTree()

            :_root(nullptr)

        {}




        BSTree(const BSTree<K,V>& t)

        {

            _root = Copy(t._root);

        }




        BSTree<K,V>& operator=(BSTree<K,V> t)

        {

            swap(_root, t._root);

            return *this;

        }




        ~BSTree()

        {

            Destroy(_root);

        }




        void InOrder()

        {

            _InOrder(_root);

            cout << endl;

        }




        Node* FindR(const K& key)

        {

            return _FindR(_root, key);

        }




        bool InsertR(const K& key, const V& value)

        {

            return _InsertR(_root, key, value);

        }




        bool EraseR(const K& key)

        {

            return _EraseR(_root, key);

        }




    private:

        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;

        }




        void Destroy(Node*& root)

        {

            if (root == nullptr)

                return;

            Destroy(root->_left);

            Destroy(root->_right);

            delete root;

            root == nullptr;

        }




        bool _EraseR(Node*& root, const K& key)

        {

            if (root == nullptr)

            {

                return false;

            }

            if (root->_key < key)

            {

                _EraseR(root->_right, key);

            }

            else if (root->_key > key)

            {

                _EraseR(root->_left, key);

            }

            else

            {

                Node* del = root;

                if (root->_left == nullptr)

                {

                    root = root->_right;

                }

                else if (root->_right == nullptr)

                {

                    root = root->_left;

                }

                else

                {

                    Node* leftMax = root->_left;

                    while (leftMax->_right)

                    {

                        leftMax = leftMax->_right;

                    }

                    swap(root->_key, leftMax->_key);

                    return _EraseR(root->_left, key);

                }

                delete del;

                return true;

            }

        }




        bool _InsertR(Node*& root, const K& key, const V& value)

        {

            if (root == nullptr)

            {

                root = new Node(key,value);

                return true;

            }

            if (root->_key < key)

            {

                _InsertR(root->_right, key, value);

            }

            else if (root->_key > key)

            {

                _InsertR(root->_left, key, value);

            }

            else

            {

                return false;

            }

        }




        Node* _FindR(Node* root, const K& key)

        {

            if (root == nullptr)

            {

                return nullptr;

            }

            if (root->_key < key)

            {

                _FindR(root->_right, key);

            }

            else if (root->_key > key)

            {

                _FindR(root->_left, key);

            }

            else

            {

                return root;

            }

        }




        void _InOrder(Node* root)

        {

            if (root == nullptr)

                return;

            _InOrder(root->_left);

            cout << root->_key << ":" << root->_value << endl;

            _InOrder(root->_right);

        }




        Node* _root;

    };




    void TestBSTree1()//小字典

    {

        BSTree<string, string> dict;

        dict.InsertR("insert", "插入");

        dict.InsertR("sort", "排序");

        dict.InsertR("right", "右边");

        dict.InsertR("date", "日期");




        string str;

        while (cin >> str)    //按Ctrl+Z终止输入

        {

            BSTreeNode<string, string>* ret = dict.FindR(str);

            if (ret)

            {

                cout << ret->_value << endl;

            }

            else

            {

                cout << "无该单词" << endl;

            }

        }

    }




    void TestBSTree2()//统计水果出现次数

    {

        string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };

        BSTree<string, int> countTree;

        for (const auto& str : arr)

        {

            // 先查找水果在不在搜索树中

            // 1、不在,说明水果第一次出现,则插入<水果, 1>

            // 2、在,则查找到的节点中水果对应的次数++

            BSTreeNode<string, int>* ret = countTree.FindR(str);

            if (ret == NULL)

            {

                countTree.InsertR(str, 1);

            }

            else

            {

                ret->_value++;

            }

        }

        countTree.InOrder();

    }

}

📌二叉搜索(排序)的性能分析

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

        对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
        但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

​编辑

• 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:

• 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:


结语

希望篇关于 二叉搜索(排序) 的博客能大家有所帮助,迎大佬留言或私信与我交流.

学海漫浩浩,我亦苦作舟!关注我,大家一起学,一起!

相关文章推荐

【数据 构】什么是 线 性表 ?

【数据 构】 线 性表的 式存 储结

【数据 构】什么是 ?

【数据 构】什么是 ?

【数据 构】什么是 ?

【数据 构】什么是堆 ?

【数据 构】什么是 ?

【数据 构】什么是二叉 ?


​编辑

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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