当前位置: 首页 > news >正文

C++二叉搜索树

c++ 二叉搜索树

1、二叉搜索树的概念

二叉搜索树又被称为二叉排序树,他是一颗空树,或者是具有以下的性质:

  • 如果他的左子树不是空的,则他的左子树节点的值小于等于根节点的值
  • 若它的右子树节点不为空,则它的右子树上所有的节点的值都大于根节点的值
  • 他的左右子树也分别为二叉搜索树
  • 二叉搜索树中可以支持插入相同值的节点,也可以不支持插入·相同节点的值,具体的情况要看具体的场景,后后续我们就会学习到map/set/multimap/mulset系列的容器,他们的底层其实就是热茶搜索树,,其中map/set不支持等值的插入,另外两个就是支持等值插入的。

下面就是一颗二叉搜索树:

8
3
10
1
6
4
7
14
13

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

最有情况下,二叉搜索树为完全二叉树(或者是接近完全二叉树),其高度是 log ⁡ 2 N \log_2N log2N
在最差的情况下,二叉搜索树会退化为单枝树,其高度为 N N N
所以在综合情况下二叉树的增删查改就是 O ( N ) O(N) O(N)
那么这样的效率显是无法满足我们的需求的,我们后续还会讲解二叉搜索树的变形AVL树和红黑树,才能适用于我们显示中的需求。
另外需要进行说明的是,二分查找也可以是现在 log ⁡ 2 N \log_2N log2N的级别的查找效率,到那时二分查找会有两个致命的缺陷:

  • 需要储存在你呢挂钩进行你那个随机访问的结构中,且数据必须有序。

  • 2.插入和删除数据的效率非常底下,因为存储在下标随机访问的数据结构中,插入和

  • 删除数据的时候一般需要挪动数据。

    这里也就体现了二叉平衡树的价值。

3.二叉搜索的插入

插入的具体过程如下:

  1. 数为空的时候,则直接新增节点,赋值给root指针
  2. 树不为空的时候,按照二叉搜索树的性质,插入这个值的结果就是直接问题的显示!!!
  3. 如果支持插入相等的值,插入值更当前的节点的值相等那就将节点插到左边也行右边也可以。找到空位置就直接站位,插入新的节点。注意要保持逻辑的连贯性,一致性,插入相等的值不要一会往右一会往左

4.二叉搜索树的查找

  1. 从跟开始查找x, x比节点的值大,则往右边找,反之往左边
  2. 最多查找树的高度次,走到空还没有找到,则就表示这棵树中没有要找的这个值。
  3. 如果不支持插入相等的值,找到x返回即可。
  4. 如果支持插入相等的值,意味着多个x存在,一般在这样的情况下,我们就被要求查找中序的x如下面的图,查找3我们就直接1的右孩子返回那个3

5. 二叉搜索树的删除

首先查找元素是否存在二叉搜索树中,如果不存在则返回false.
如果查找的元素存在则分为一下四种情况进行分别处理:(假设删除的节点为N)

  1. 要删除的节点N的左右孩子全为空。
  2. 要删除的节点的N左节点为空,右不为空
  3. 要删除的节点N右孩子为为空,左孩子不为空
  4. 要删除的节点的N的左右孩子均不为空

对应以上四种情况的解决方案:
  1. 把N节点的父亲对应的孩子指针指向空,直接删除N节点(情况1可以当成2或者是3处理,效果是一样的)
  2. 把N节点的父亲节点对应的孩子指针指向N的有孩子,直接删除N节点。
  3. 把N节点的父亲对应的孩子指针指向N的左孩子,直接删除N节点。
  4. 无法直接删除N节点,因为N的两个孩子无处安放,只能使用替代法进行删除。找到左子树的的值最大的节点R(最右节点)或则是N右子树的值最小的节点R(最左节点)代替这个N的节点,因为这两个节点中的任意一个,放到N的位置,都是,妈祖二叉搜索树的规则的。代替N的节点的意思就是N和R的两个节点进行交换,然后再删除R节点位置的值,R节点符合情况2或者是情况3都可以直接删除!!!


6. 二叉搜索树的代码实现

首先我们需要搭建叉搜索树的基本结构

节点的构建

template<class k>
// 先来定义二叉搜索树的基本结构,这个就是他们的节点设置!!!
struct bstNode
{
    K _key;
    bstNode<k>* _left;
    bstNode<k>* _right;

    bstNode(const k& key)
    :_left(nullptr), _right(nullptr), _key(key)
    {}
};

完成节点的定义时,我们就需要对树型结构的搭建,现在我们就按照树形结构的定义来实现规则。

6.1 树形结构的搭建

template<class k>
class bstree  
{
  typedef bstNode<k> node;
public:

private:
  node<k>* _root;
};

按照我们之前的习惯我们还是先来创建二叉树的基本结构,以前在学习二叉树的时候我们已经接触了许多了,所以我们这里就不再进行详细的解说了。

6.2 二叉搜索树的插入实现

根据前面的分析现在我们就按照前面的理论基础进行相应的树形结构建立。
在插入节点的过程中我们需要注意这三种情况:

  1. 插入时_root为空节点,直接新增节点,赋值给root指针
  2. 不为空的时候就按照二叉搜索树的性质直接按照大右小左的规则直接往下走就好了。
  3. 如果支持插入相等的值,我们就要统一规则,往右插入节点还是往左插入节点。
  • 首先我们就来解决这个当插入节点时,_root为空节点的情况
bool insert(const k key)
{
  // zdl:: 此时_root == nullptr
    if (!_root)
    {
        auto x = node(key);
        _root->_left = new node(key);
        return true;
    }
    else
    {
        
    }
}

否则我们就直接按照规则往下插入节点

bool insert(const k& key)
        {
            if (!_root)
            {
                _root = new node(key);
                return true;
            }
            else
            {
                node* cur = _root, *parent = nullptr;
                while (cur)
                {
                    if (cur->_key > key)
                    {
                        parent = cur;
                        cur = cur->_left;
                    }
                    else if (cur->_key < key)
                    {
                        parent = cur;
                        cur = cur->_right;
                    }
                    else return false;
                }
                cur = new node(key);
                if (key > parent->_key)
                {
                    parent->_right = cur;
                }
                else
                {
                    parent->_left = cur;
                }
            }

            return true;
        }

二叉树的插入节点代码十分的简单,我们按照节点的插入规则,直接就完成了现在的操作。
按照二叉树的插入规则,在进行二叉搜索树的中序遍历时,我们得到的将会是一个有序的数组。

void _InOrder(node* root)
{
    if (root)
    {
        _InOrder(root->_left);
        cout << root->_key << " ";
        _InOrder(root->_right);
    }
}

现在我们就来验证一下我们实现的代码有没有问题:

从上面的代码来看,我们的插入操作没有问题。


#include"BST.h"
using namespace zdl;
int main()
{
    int a[] = {2,43,4,5,7,2,4,5,2,9,0};
    zdl::bstree<int> bst;
    for (auto& x : a) bst.insert(x);
    bst.InOrder();
    return 0;
}

6.3 二叉搜索树的特定值的查找

查找的逻辑就看看就很简单,我们就是按照规则向下查找特定的值,找到就返回真,否则返回假。

 bool find(const k& key)
{
    node* cur = _root;
    while (cur)
    {
        if (cur->_key > key)
        {
            cur = cur->_left;
        }
        else if (cur->_key < key)
        {
            cur = cur->_right;
        }
        else return true;
    }
    return false;
}

6.4 二叉搜索树的删除

  1. 想要删除相应值的节点我们就必须要找到这个指定的节点并且在还要知道他的父节点。
 bool Erase(const K &key)
{
    Node *parent = nullptr;
    Node *cur = _root;
    while (cur)
    {
        if (cur->_key < key)
        {
            parent = cur;
            cur = cur->_right;
        }
        else if (cur->_key > key)
        {
            parent = cur;
            cur = cur->_left;
        }
        else
        {
            // 0-1个孩⼦的情况
            // 删除情况1 2 3均可以直接删除,改变⽗亲对应孩⼦指针指向即可
            if (cur->_left == nullptr)
            {
                if (parent == nullptr)
                {
                    _root = cur->_right;
                }
                else
                {
                    if (parent->_left == cur)
                        parent->_left = cur->_right;
                    else
                        parent->_right = cur->_right;
                }
                delete cur;
                return true;
            }
            else if (cur->_right == nullptr)
            {
                if (parent == nullptr)
                {
                    _root = cur->_left;
                }
                else
                {
                    if (parent->_left == cur)
                        parent->_left = cur->_left;
                    else
                        parent->_right = cur->_left;
                }
                delete cur;
                return true;
            }
            else
            {
                // 2个孩⼦的情况
                // 删除情况4,替换法删除
                // 假设这⾥我们取右⼦树的最⼩结点作为替代结点去删除
                // 这⾥尤其要注意右⼦树的根就是最⼩情况的情况的处理,对应课件图中删8的情况
                // ⼀定要把cur给rightMinP,否会报错。
                Node *rightMinP = cur;
                Node *rightMin = cur->_right;
                while (rightMin->_left)
                {
                    rightMinP = rightMin;
                    rightMin = rightMin->_left;
                }
                cur->_key = rightMin->_key;
                if (rightMinP->_left == rightMin)
                    rightMinP->_left = rightMin->_right;
                else
                    rightMinP->_right = rightMin->_right;
                delete rightMin;
                return true;
            }
        }
    }
    return false;
}
void InOrder()
{
    _InOrder(_root);
    cout << endl;
}

根据上面的定义,我们很轻松就可以直接得到,删除的逻辑,在进行删除时,我们需要考虑到的无非就是这几种情况,考虑到孩子的安放,并且考虑如果删除时,删除的正好是根节点又该怎么办,只需要将上面的问题搞清楚,这个问题就算是解决了!!!

7. key/vaule 的场景应用

每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字⾛⼆叉搜索树的规则进⾏⽐较,可以快速查找到key对应的value。key/value的搜索场景实现的⼆叉树搜索树⽀持修改,但是不⽀持修改key,修改key破坏搜索树性质了,可以修改value。场景1:简单中英互译字典,树的结构中(结点)存储key(英⽂)和vlaue(中⽂),搜索时输⼊英⽂,则同时查找到了英⽂对应的中⽂。场景2:商场⽆⼈值守⻋库,⼊⼝进场时扫描⻋牌,记录⻋牌和⼊场时间,出⼝离场时,扫描⻋牌,查找⼊场时间,⽤当前时间-⼊场时间计算出停⻋时⻓,计算出停⻋费⽤,缴费后抬杆,⻋辆离场。场景3:统计⼀篇⽂章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。

所以基于上面的代码,我们就是已经这个二叉搜索树的基本代码都已经实现,现在我们就是基于pair类型将这个代码进行进一步的加工。

template<class K, class V>
struct BSTNode
{
 // pair<K, V> _kv;
 K _key;
 V _value;
 BSTNode<K, V>* _left;
 BSTNode<K, V>* _right;
 BSTNode(const K& key, const V& value)
:_key(key)
 , _value(value)
 , _left(nullptr)
 , _right(nullptr)
 {}
};
template<class K, class V>
class BSTree
{
 typedef BSTNode<K, V> Node;
public:
 BSTree() = default;
 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);
 _root = nullptr;
 }
 bool Insert(const K& key, const V& value)
 {
 if (_root == nullptr)
 {
 _root = new Node(key, value);
 return true;
 }
 Node* parent = nullptr;
 Node* cur = _root;
 while (cur)
 {
 if (cur->_key < key)
 {
 parent = cur;
 cur = cur->_right;
}
 else if (cur->_key > key)
 {
 parent = cur;
 cur = cur->_left;
 }
 else
 {
 return false;
 }
 }
 cur = new Node(key, value);
 if (parent->_key < key)
 {
 parent->_right = cur;
 }
 else
 {
 parent->_left = cur;
 }
 return true;
 }
 Node* Find(const K& key)
 {
 Node* cur = _root;
 while (cur)
 {
 if (cur->_key < key)
 {
 cur = cur->_right;
 }
 else if (cur->_key > key)
 {
 cur = cur->_left;
 }
 else
 {
 return cur;
 }
 }
 return nullptr;
 }
  bool Erase(const K& key)
 {
 Node* parent = nullptr;
 Node* cur = _root;
 while (cur)
 {
 if (cur->_key < key)
 {
 parent = cur;
 cur = cur->_right;
 }
 else if (cur->_key > key)
 {
 parent = cur;
 cur = cur->_left;
 }
 else
 {
 if (cur->_left == nullptr)
 {
 if (parent == nullptr)
 {
 _root = cur->_right;
 }
 else
 {
 if (parent->_left == cur)
 parent->_left = cur->_right;
 else
 parent->_right = cur->_right;
 }
 delete cur;
 return true;
 }
 else if (cur->_right == nullptr)
 {
 if (parent == nullptr)
 {
 _root = cur->_left;
 }
 else
 {
 if (parent->_left == cur)
 parent->_left = cur->_left;
 else
 parent->_right = cur->_left;
}
 delete cur;
 return true;
 }
 else
 {
 Node* rightMinP = cur;
 Node* rightMin = cur->_right;
 while (rightMin->_left)
 {
 rightMinP = rightMin;
 rightMin = rightMin->_left;
 }
 cur->_key = rightMin->_key;
 if (rightMinP->_left == rightMin)
 rightMinP->_left = rightMin->_right;
 else
 rightMinP->_right = rightMin->_right;
 delete rightMin;
 return true;
 }
 }
 }
 return false;
 }
 void InOrder()
 {
 _InOrder(_root);
 cout << endl;
 }
private:
    void _InOrder(Node* root)
    {
        if (root == nullptr)
        {
            return;
        }
        _InOrder(root->_left);
        cout << root->_key << ":" << root->_value << endl;
        _InOrder(root->_right);
        }
        void Destroy(Node* root)
        {
            if (root == nullptr)
            return;
            Destroy(root->_left);
            Destroy(root->_right);
            delete root;
        }
        Node* Copy(Node* root)
        {
            if (root == nullptr)
            return nullptr;
            Node* newRoot = new Node(root->_key, root->_value);
            newRoot->_left = Copy(root->_left);
            newRoot->_right = Copy(root->_right);
            return newRoot;
        }
    private:
    Node* _root = nullptr;
};
int main()
{
    BSTree<string, string> dict;
    //BSTree<string, string> copy = dict;
    dict.Insert("left", "左边");
    dict.Insert("right", "右边");
    dict.Insert("insert", "插⼊");
    dict.Insert("string", "字符串");
    string str;
    while (cin>>str)
    {
        auto ret = dict.Find(str);
        if (ret)
        {
            cout << "->" << ret->_value << endl;
        }
        else
        {
            cout << "⽆此单词,请重新输⼊" << endl;
        }
    }
    return 0;
}
// 测试二
int main()
{
    string arr[] = { "苹果", "西⽠", "苹果", "西⽠", "苹果", "苹果", "西⽠", "苹
    果", "⾹蕉", "苹果", "⾹蕉" };
    BSTree<string, int> countTree;
    for (const auto& str : arr)
    {
        // 先查找⽔果在不在搜索树中 
        // 1、不在,说明⽔果第⼀次出现,则插⼊<⽔果, 1> 
        // 2、在,则查找到的结点中⽔果对应的次数++ 
        //BSTreeNode<string, int>* ret = countTree.Find(str);
        auto ret = countTree.Find(str);
        if (ret == NULL)
        {
            countTree.Insert(str, 1);
        }
        else
        {
            ret->_value++;
        }
    }
    countTree.InOrder();
    return 0;
}


好,今天关于二叉搜索树的学习就到这里,我们下期再见,拜拜!!!🌈✈️😄

相关文章:

  • Linux-SaltStack配置
  • 【NLP 37、激活函数 ③ relu激活函数】
  • 注意力机制在 Transformer 模型中的核心作用剖析
  • 部署若依微服务遇到的坑
  • 如何安装vm和centos
  • C++ lambda表达式
  • DeepSeek 15天指导手册——从入门到精通 PDF(附下载)
  • 机器学习基础入门——机器学习库介绍(NumPy、pandas、Matplotlib)
  • C/C++后端开发面试表述、技术点摸底——基础组件篇
  • VM C#脚本 调用命令行 以python为例
  • Python在大数据AI领域的优势分析【为什么使用Python开发】
  • git设置本地代理
  • 基于深度学习的SSD口罩识别项目完整资料版(视频教程+课件+源码+数据)
  • 矩阵乘积态简介
  • 设备能够连接WiFi,能ping通百度,但是网页无法打开显示没有网络
  • 力扣leetcode 21. 合并两个有序链表 递归 C语言解法
  • 如何生成traceid以及可视化展示
  • 【Java毕业设计】商城购物系统(附源码+数据库脚本)
  • Directed acyclic graph [DAG]有向无环图 应用场景汇总与知名开源库实现细节说明
  • Junit+Mock
  • 国外网站建设素材/关键词优化怎么弄
  • 怎么做百度网站验证码/商业软文代写
  • 大网站建设/seo流量增长策略
  • b2b网站备案/如何注册百度账号
  • 海外版tiktok免费入口/seo有什么作用
  • 建设网站需要花费什么费用/第三波疫情将全面大爆发