【C++ 学习 ⑳】- 详解二叉搜索树

news2024/12/29 17:46:07

目录

一、概念

二、实现

2.1 - BST.h

2.2 - test.cpp

三、应用

四、性能分析


 


一、概念

二叉搜索树(BST,Binary Search Tree),又称二叉排序树二叉查找树

二叉搜索树是一棵二叉树,可以为空;如果不为空,满足以下性质:

  • 非空左子树的所有键值小于其根节点的键值

  • 非空右子树的所有键值大于其根节点的键值

  • 左、右子树本身也都是二叉搜索树。


二、实现

2.1 - BST.h

#pragma once
​
#include <stack>
#include <iostream>
​
template<class K>
struct BSTNode
{
    BSTNode<K>* _left;
    BSTNode<K>* _right;
    K _key;
​
    BSTNode(const K& key = K())
        : _left(nullptr), _right(nullptr), _key(key)
    { }
};
​
template<class K>
class BST
{
    typedef BSTNode<K> BSTNode;
​
public:
    /*---------- 构造函数和析构函数 ----------*/
    // 默认构造函数
    BST() : _root(nullptr)
    { }
​
    // 拷贝构造函数(实现深拷贝)
    BST(const BST<K>& t)
    {
        _root = Copy(t._root);
    }
​
    // 析构函数
    ~BST()
    {
        Destroy(_root);
    }
​
    /*---------- 赋值运算符重载 ----------*/
    // 利用上面写好的拷贝构造函数实现深拷贝
    BST<K>& operator=(BST<K> tmp)
    {
        std::swap(_root, tmp._root);
        return *this;
    }
​
    /*---------- 插入 ----------*/
    // 非递归写法
    bool Insert(const K& key)
    {
        if (_root == nullptr)
        {
            _root = new BSTNode(key);
            return true;
        }
​
        BSTNode* parent = nullptr;
        BSTNode* cur = _root;
        while (cur)
        {
            parent = cur;
            if (key < cur->_key)
                cur = cur->_left;
            else if (key > cur->_key)
                cur = cur->_right;
            else
                return false;
        }
​
        cur = new BSTNode(key);
        if (key < parent->_key)
            parent->_left = cur;
        else
            parent->_right = cur;
        return true;
    }
​
    // 递归(Recursion)写法
    bool InsertRecursion(const K& key)
    {
        return _InsertRecursion(_root, key);
    }
​
    /*---------- 删除 ----------*/
    // 非递归写法
    bool Erase(const K& key)
    {
        BSTNode* parent = nullptr;
        BSTNode* cur = _root;
        while (cur)
        {
            if (key < cur->_key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else if (key > cur->_key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else
            {
                if (cur->_left == nullptr)  // 左子树为空
                {
                    if (parent == nullptr)  // 或者 cur == _root
                    {
                        _root = cur->_right;
                    }
                    else
                    {
                        if (parent->_left == cur)
                            parent->_left = cur->_right;
                        else
                            parent->_right = cur->_right;
                    }
                }
                else if (cur->_right == nullptr)  // 右子树为空
                {
                    if (parent == nullptr)  // 或者 cur == _root
                    {
                        _root = cur->_left;
                    }
                    else
                    {
                        if (parent->_left == cur)
                            parent->_left = cur->_left;
                        else
                            parent->_right = cur->_left;
                    }
                }
                else  // 左、右子树非空
                {
                    // 找到左子树中关键字最大的节点(或者找到右子树中关键字最小的节点)
                    BSTNode* parentLeftMax = cur;
                    BSTNode* leftMax = cur->_left;
                    while (leftMax->_right)
                    {
                        parentLeftMax = leftMax;
                        leftMax = leftMax->_right;
                    }
                    // 让 leftMax 指向的节点代替 cur 指向的节点,然后删除 leftMax 指向的节点,
                    // 这样就转换成了上面的情况
                    std::swap(cur->_key, leftMax->_key);
                    if (parentLeftMax->_left == leftMax)  // 或者 parentLeftMax == cur
                        parentLeftMax->_left = leftMax->_left;
                    else
                        parentLeftMax->_right = leftMax->_left;
                    cur = leftMax;
                }
                delete cur;
                return true;
            }
        }
        return false;
    }
​
    // 递归写法
    bool EraseRecursion(const K& key)
    {
        return _EraseRecursion(_root, key);
    }
​
    /*---------- 查找 ----------*/
    // 非递归写法
    bool Search(const K& key) const
    {
        BSTNode* cur = _root;
        while (cur)
        {
            if (key < cur->_key)
                cur = cur->_left;
            else if (key > cur->_key)
                cur = cur->_right;
            else
                return true;
        }
        return false;
    }
​
    // 递归写法
    bool SearchRecursion(const K& key) const
    {
        return _SearchRecursion(_root, key);
    }
​
    /*---------- 中序遍历 ----------*/
    // 非递归写法
    void InOrder() const
    {
        std::stack<BSTNode*> st;
        BSTNode* cur = _root;
        while (cur || !st.empty())
        {
            while (cur)
            {
                st.push(cur);
                cur = cur->_left;
            }
            BSTNode* top = st.top();
            st.pop();
            std::cout << top->_key << " ";
            cur = top->_right;
        }
        std::cout << std::endl;
    }
​
    // 递归写法
    void InOrderRecursion() const
    {
        _InOrderRecursion(_root);
        std::cout << std::endl;
    }
​
private:
    BSTNode* Copy(BSTNode* root)
    {
        if (root == nullptr)
            return nullptr;
​
        BSTNode* copyRoot = new BSTNode(root->_key);
        copyRoot->_left = Copy(root->_left);
        copyRoot->_right = Copy(root->_right);
        return copyRoot;
    }
​
    void Destroy(BSTNode*& root)
    {
        // 【注意:root 为 _root 或者某个节点的左或右指针的引用】
        if (root == nullptr)
            return;
​
        Destroy(root->_left);
        Destroy(root->_right);
        delete root;
        root = nullptr;
    }
​
    bool _InsertRecursion(BSTNode*& root, const K& key)
    {
        // 【注意:root 为 _root 或者某个节点的左或右指针的引用】
        if (root == nullptr)
        {
            root = new BSTNode(key);
            return true;
        }
​
        if (key < root->_key)
            _InsertRecursion(root->_left, key);
        else if (key > root->_key)
            _InsertRecursion(root->_right, key);
        else
            return false;
    }
​
    bool _EraseRecursion(BSTNode*& root, const K& key)
    {
        // 【注意:root 为 _root 或者某个节点的左或右指针的引用】
        if (root == nullptr)
            return false;
​
        if (key < root->_key)
            _EraseRecursion(root->_left, key);
        else if (key > root->_key)
            _EraseRecursion(root->_right, key);
        else
        {
            BSTNode* tmp = root;
            if (root->_left == nullptr)
                root = root->_right;
            else if (root->_right == nullptr)
                root = root->_left;
            else
            {
                BSTNode* leftMax = root->_left;
                while (leftMax->_right)
                {
                    leftMax = leftMax->_right;
                }
                std::swap(leftMax->_key, root->_key);
                return _EraseRecursion(root->_left, key);
            }
            delete tmp;
            return true;
        }
    }
​
    bool _SearchRecursion(BSTNode* root, const K& key) const
    {
        if (root == nullptr)
            return false;
​
        if (key < root->_key)
            _SearchRecursion(root->_left, key);
        else if (key > root->_key)
            _SearchRecursion(root->_right, key);
        else
            return true;
    }
​
    void _InOrderRecursion(BSTNode* root) const
    {
        if (root == nullptr)
            return;
​
        _InOrderRecursion(root->_left);
        std::cout << root->_key << " ";
        _InOrderRecursion(root->_right);
    }
​
private:
    BSTNode* _root;
};

2.2 - test.cpp

#include "BST.h"
using namespace std;
​
void TestBST1()
{
    int arr[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
    BST<int> t1;
    for (auto e : arr)
    {
        t1.Insert(e);
    }
    t1.InOrder();  // 1 3 4 6 7 8 10 13 14
​
    BST<int> t2(t1);
    t2.InOrder();  // 1 3 4 6 7 8 10 13 14
​
    BST<int> t3;
    t3 = t1;
    t1.InOrder();  // 1 3 4 6 7 8 10 13 14
​
    // 左子树为空
    t1.Erase(4);
    t1.InOrder();  // 1 3 6 7 8 10 13 14
    t1.Erase(6);
    t1.InOrder();  // 1 3 7 8 10 13 14
    // 右子树为空
    t1.Erase(14);
    t1.InOrder();  // 1 3 7 8 10 13
    // 左、右子树非空
    t1.Erase(8);
    t1.InOrder();  // 1 3 7 10 13
​
    cout << t1.Search(8) << endl;  // 0
    cout << t1.Search(7) << endl;  // 1
}
​
void TestBST2()
{
    int arr[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
    BST<int> t;
    for (auto e : arr)
    {
        t.InsertRecursion(e);
    }
    t.InOrderRecursion();  // 1 3 4 6 7 8 10 13 14
​
    // 左子树为空
    t.EraseRecursion(4);
    t.InOrderRecursion();  // 1 3 6 7 8 10 13 14
    t.EraseRecursion(6);
    t.InOrderRecursion();  // 1 3 7 8 10 13 14
    // 右子树为空
    t.EraseRecursion(14);
    t.InOrderRecursion();  // 1 3 7 8 10 13
    // 左、右子树非空
    t.EraseRecursion(8);
    t.InOrderRecursion();  // 1 3 7 10 13
​
    cout << t.SearchRecursion(8) << endl;  // 0
    cout << t.SearchRecursion(7) << endl;  // 1
}
​
int main()
{
    TestBST1();
    TestBST2();
    return 0;
}

 


三、应用

  1. K 模型:结构体中只需要存储关键码 key,关键码即为需要搜索到的值

    例如,要判断一个单词是否拼写正确,我们首先把词库中的每个单词作为 key,构建一棵二叉搜索树,然后在这棵二叉搜索树中检索单词是否存在,若存在则表明拼写正确,不存在则表明拼写错误。

  2. KV 模型:每个关键码 key,都有与之对应的值 value,即 <key, value> 的键值对

    这种模型在现实生活中也很常见:

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

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

    BST.h

    #pragma once
    ​
    #include <iostream>
    ​
    template<class K, class V>
    struct BSTNode
    {
        BSTNode<K, V>* _left;
        BSTNode<K, V>* _right;
        K _key;
        V _value;
    ​
        BSTNode(const K& key = K(), const V& value = V())
            : _left(nullptr), _right(nullptr), _key(key), _value(value)
        { }
    };
    ​
    template<class K, class V>
    class BST
    {
        typedef BSTNode<K, V> BSTNode;
    ​
    public:
        BST() : _root(nullptr)
        { }
    ​
        BST(const BST<K, V>& t)
        {
            _root = Copy(t._root);
        }
    ​
        ~BST()
        {
            Destroy(_root);
        }
    ​
        BST<K, V>& operator=(BST<K, V> tmp)
        {
            std::swap(_root, tmp._root);
            return *this;
        }
    ​
        bool Insert(const K& key, const V& value)
        {
            return _Insert(_root, key, value);
        }
    ​
        bool Erase(const K& key)
        {
            return _Erase(_root, key);
        }
    ​
        BSTNode* Search(const K& key) const
        {
            return _Search(_root, key);
        }
    ​
        void InOrder() const
        {
            _InOrder(_root);
            std::cout << std::endl;
        }
    ​
    private:
        BSTNode* Copy(BSTNode* root)
        {
            if (root == nullptr)
                return nullptr;
    ​
            BSTNode* copyRoot = new BSTNode(root->_key, root->_value);
            copyRoot->_left = Copy(root->_left);
            copyRoot->_right = Copy(root->_right);
            return copyRoot;
        }
    ​
        void Destroy(BSTNode*& root)
        {
            // 【注意:root 为 _root 或者某个节点的左或右指针的引用】
            if (root == nullptr)
                return;
    ​
            Destroy(root->_left);
            Destroy(root->_right);
            delete root;
            root = nullptr;
        }
    ​
        bool _Insert(BSTNode*& root, const K& key, const V& value)
        {
            // 【注意:root 为 _root 或者某个节点的左或右指针的引用】
            if (root == nullptr)
            {
                root = new BSTNode(key, value);
                return true;
            }
    ​
            if (key < root->_key)
                _Insert(root->_left, key, value);
            else if (key > root->_key)
                _Insert(root->_right, key, value);
            else
                return false;
        }
    ​
        bool _Erase(BSTNode*& root, const K& key)
        {
            // 【注意:root 为 _root 或者某个节点的左或右指针的引用】
            if (root == nullptr)
                return false;
    ​
            if (key < root->_key)
                _Erase(root->_left, key);
            else if (key > root->_key)
                _Erase(root->_right, key);
            else
            {
                BSTNode* tmp = root;
                if (root->_left == nullptr)
                    root = root->_right;
                else if (root->_right == nullptr)
                    root = root->_left;
                else
                {
                    BSTNode* leftMax = root->_left;
                    while (leftMax->_right)
                    {
                        leftMax = leftMax->_right;
                    }
                    std::swap(leftMax->_key, root->_key);
                    return _Erase(root->_left, key);
                }
                delete tmp;
                return true;
            }
        }
    ​
        BSTNode* _Search(BSTNode* root, const K& key) const
        {
            if (root == nullptr)
                return nullptr;
    ​
            if (key < root->_key)
                _Search(root->_left, key);
            else if (key > root->_key)
                _Search(root->_right, key);
            else
                return root;
        }
    ​
        void _InOrder(BSTNode* root) const
        {
            if (root == nullptr)
                return;
    ​
            _InOrder(root->_left);
            std::cout << root->_key << " : " << root->_value << std::endl;
            _InOrder(root->_right);
        }
    private:
        BSTNode* _root;
    };

    test.cpp

    #include "BST_KV.h"
    using namespace std;
    ​
    void TestBST1()
    {
        BST<string, string> t;
        t.Insert("insert", "插入");
        t.Insert("erase", "删除");
        t.Insert("search", "查找");
        t.Insert("left", "左边");
        t.Insert("right", "右边");
        // 输入英文单词,找到对应的中文
        string str;
        while (cin >> str)
        {
            BSTNode<string, string>* ret = t.Search(str);
            if (ret)
                cout << str << "对应的中文是:" << ret->_value << endl;
            else
                cout << "单词拼写错误,词库中没有此单词!" << endl;
        }
    }
    ​
    void TestBST2()
    {
        string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", 
            "苹果", "香蕉", "苹果", "香蕉" };
        BST<string, int> t;
        // 统计每种水果出现的次数
        for (const auto& str : arr)
        {
            BSTNode<string, int>* ret = t.Search(str);
            if (ret == nullptr)
                t.Insert(str, 1);
            else
                ret->_value += 1;
        }
        t.InOrder();
        // 苹果 : 6
        // 西瓜: 3
        // 香蕉 : 2
    }
    ​
    int main()
    {
        // TestBST1();
        TestBST2();
        return 0;
    }


四、性能分析

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

对含 n 个节点的二叉搜索树,若每个节点查找的概率相等,则二叉搜树的平均查找长度是节点在二叉搜树树的深度的函数,即节点越深,比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树,例如:

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

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

如果退化成单支树,二叉搜树的性能就丢失了,那么能否改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?

后续所要学习的 AVL 树和红黑树就可以解决上述问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/979338.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Langchain使用之 - 文本分割Splitter

Langchain提供了多种文本分割器&#xff0c;包括CharacterTextSplitter(),MarkdownHeaderTextSplitter(),RecursiveCharacterTextSplitter()等&#xff0c;各种Splitter的作用如下图所示&#xff1a; TextSplitter 下面的代码是使用RecursiveCharacterTextSplitter对一段文字进…

vue-tour新手指导,点击按钮,进行提示,再次点击按钮,提示隐藏,点击下一步,弹框显示

先看效果图 main.js中引入vue-tour import VueTour from vue-tour require(vue-tour/dist/vue-tour.css) Vue.use(VueTour)建一个登录页面 点击导航助手按钮&#xff0c;开始提示 <el-button type"primary" plain click"startTour">导航助…

手写Spring:第9章-Aware感知容器对象

文章目录 一、目标&#xff1a;Aware感知容器对象二、设计&#xff1a;Aware感知容器对象三、实现&#xff1a;Aware感知容器对象3.1 工程结构3.2 Spring感知接口类图3.3 定义标记接口和容器感知类3.3.1 定义标记接口3.3.2 对象工厂感知接口3.3.3 类加载感知接口3.3.4 对象名称…

智慧排水监测系统:实时监测城市排水情况

中国智慧城市概念最初由住建部提出&#xff0c;随着智慧城市建设的广泛实践&#xff0c;对其认知也在不断深入与变化。2014年&#xff0c;国家发改委从数字化与技术角度认为:智慧城市是运用物联网、云计算、大数据、空间地理信息集成等新一代信息技术&#xff0c;促进城市规划、…

实现SSE的textevent-stream是什么?和applicationoctet-stream有什么区别?

WEB通讯技术。前端实现SSE长连接&#xff0c;nodejsexpress搭建简单服务器&#xff0c;进行接口调试&#xff0c;通过curl请求数据 点击上面的地址是可以了解轮询和长轮询以及websocket等通信模式&#xff0c;一些基础概念和速成技能&#xff0c;这篇来接着详细聊聊text/event…

电影《孤注一掷》引发观众思考网络安全

近日上映的电影《孤注一掷》深刻地揭示了境外网络诈骗的全产业链&#xff0c;上万起真实诈骗案例为素材&#xff0c;让观众近距离感受这一犯罪行为的阴谋与可怕。影片呈现了从诈骗策划到资金流转的每一个环节&#xff0c;引发了观众的强烈好奇和观看欲望。这种真实性让观众对网…

MITSUBISHI A1SJ51T64电源单元

电源供应&#xff1a;A1SJ51T64 电源单元通常用于为MITSUBISHI PLC系统提供稳定的电源&#xff0c;以确保系统正常运行。 电源输入&#xff1a;它通常支持广泛的电源输入范围&#xff0c;以适应不同地区的电源标准。 电源输出&#xff1a;A1SJ51T64 电源单元通常提供多个电源…

【C++基础】6、常量

文章目录 【 1、常量的分类 】1.1 整型常量1.2 浮点常量1.3 字符常量1.4 字符串常量1.5 布尔常量 【 2、常量的定义 】2.1 #define 预处理器2.2 const 关键字 常量 是固定值&#xff0c;在程序执行期间不会改变。这些固定的值&#xff0c;又叫做字面量。常量可以是任何的基本数…

22行 手写实现promise

面试题 const MyPromise()>{}const myPromise new MyPromise((resolve) > {setTimeout(() > { resolve(hellow world) }, 2000)})myPromise.then((res) > {console.log(res)return "00"}) 手写promise&#xff0c;面试了一个面试题&#xff0c;promise…

【管理运筹学】第 7 章 | 图与网络分析(1,图论背景以及基本概念、术语、矩阵表示)

文章目录 引言一、图与网络的基本知识1.1 图与网络的基本概念1.1.1 图的定义1.1.2 图中相关术语1.1.3 一些特殊图类1.1.4 图的运算 1.2 图的矩阵表示1.2.1 邻接矩阵1.2.2 可达矩阵1.2.3 关联矩阵1.2.4 权矩阵 写在最后 引言 按照正常进度应该学习动态规划了&#xff0c;但我想…

Java/Lombok Slf4j日志配置输出到文件中

1、概述 新项目需要增加日志需求&#xff0c;所以网上找了下日志配置&#xff0c;需求是将日志保存到指定文件中。网上找了下文章&#xff0c;发现没有特别完整的文章&#xff0c;下面自己整理下。 1、Java日志概述 对于一个应用程序来说日志记录是必不可少的一部分。线上问题…

每日刷题(回溯法经典问题之子集)

食用指南&#xff1a;本文为作者刷题中认为有必要记录的题目 前置知识&#xff1a;回溯法经典问题之组合 ♈️今日夜电波&#xff1a;想着你—郭顶 1:09 ━━━━━━️&#x1f49f;──────── 4:15 …

Java“牵手”微店商品列表数据,关键词搜索微店商品数据接口,微店API申请指南

微店商城是一个网上购物平台&#xff0c;售卖各类商品&#xff0c;包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取微店商品列表和商品详情页面数据&#xff0c;您可以通过开放平台的接口或者直接访问微店商城的网页来获取商品详情信息。以下是两种常用方法的介绍&…

电子元器件采购中的供应链透明度

在电子元器件采购中&#xff0c;供应链透明度是一个至关重要的因素。它指的是能够清晰、实时地了解整个供应链的运作和流程&#xff0c;以及相关的信息和数据。供应链透明度在采购中具有多方面的优势和重要性&#xff0c;包括以下方面&#xff1a; 库存管理&#xff1a; 透明的…

『C语言进阶』指针进阶(一)

&#x1f525;博客主页&#xff1a; 小羊失眠啦 &#x1f516;系列专栏&#xff1a; C语言 &#x1f325;️每日语录&#xff1a;无论你怎么选&#xff0c;都难免会有遗憾。 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 前言 在C语言初阶中&#xff0c;我们对指针有了一定的…

交叉编译嵌入式linux平台的gdb工具

目录 前期准备&#xff1a; 开始编译&#xff1a; 配置编译环境&#xff1a; 配置交叉编译工具链&#xff1a; 创建交叉编译产物的目录&#xff1a; termcap&#xff1a; ncurses&#xff1a; gmp&#xff1a; gdb&#xff1a; 编译产物&#xff1a; 前期准备&#x…

IT设备监控软件有哪些功能

IT设备监控软件通常可以实现以下功能&#xff1a;  设备状态监控&#xff1a;可以实时监测IT设备的运行状态&#xff0c;如设备的温度、湿度、风扇转速等&#xff0c;以及设备的开机、关机、重启等事件。  性能指标监控&#xff1a;可以监测IT设备的各项性能指标&#xff0…

rtthread下spi device架构MCP25625驱动

1.CAN驱动架构 由于采用了RTT的spi device架构&#xff0c;不能再随心所遇的编写CAN驱动 了&#xff0c;之前内核虽然采用了RTT内核&#xff0c;但是驱动并没有严格严格按RTT推荐的架构来做&#xff0c;这次不同了&#xff0c;上次是因为4个MCP25625挂在了4路独立的SPI总线上&…

前端(十六)——Web应用的安全性研究

&#x1f642;博主&#xff1a;小猫娃来啦 &#x1f642;文章核心&#xff1a;Web应用的安全性研究 文章目录 概述常见前端安全漏洞XSS&#xff08;跨站脚本攻击&#xff09;CSRF&#xff08;跨站请求伪造&#xff09; 点击劫持安全性验证与授权用户身份验证授权与权限管理 安全…

“搭载超快闪充、续航自由、天玑8200性能” iQOO Z8系列发布

近日&#xff0c;“天玑 8200 性能小超人”iQOO Z8系列正式发布&#xff0c;包括iQOO Z8和iQOO Z8x两款产品&#xff0c;首销售价1199元起。 “天玑 8200 性能小超人”iQOO Z8倾力打造“最佳千元性能机”&#xff1a;搭载具备巅峰性能的天玑 8200 &#xff0c;携手满血版LPDDR…