【C++高阶数据结构】B树、B+树、B*树

news2025/1/15 16:27:41

🏆个人主页:企鹅不叫的博客

​ 🌈专栏

  • C语言初阶和进阶
  • C项目
  • Leetcode刷题
  • 初阶数据结构与算法
  • C++初阶和进阶
  • 《深入理解计算机操作系统》
  • 《高质量C/C++编程》
  • Linux

⭐️ 博主码云gitee链接:代码仓库地址

⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!

💙系列文章💙

【C++高阶数据结构】并查集

【C++高阶数据结构】图

【C++高阶数据结构】LRU


文章目录

  • 💙系列文章💙
  • 💎一、概念
    • 🏆1.优点
    • 🏆2.B树规则
  • 💎二、B树插入
    • 🏆1.插入过程
    • 🏆2.代码实现
  • 💎三、B+树
    • 🏆1.概念
    • 🏆2.分裂过程
  • 💎四、B*树
    • 🏆1.概念
  • 💎五、B树系类应用
    • 🏆1.MyISAM
    • 🏆2.InnoDB


💎一、概念

🏆1.优点

B树适合外查找,当数据量很大,无法一次全部都放进内存的话,那就只能存在磁盘上,B树本质是一个多叉搜索树。

在这里插入图片描述

从树的根开始读取的话,我们需要读取树的高度次磁盘IO,多次进行磁盘读取,就会非常缓慢。每次要读取新的数据,要去定位这个过程是非常缓慢。

在平衡搜索树的基础上寻找优化方法
1.压缩高度,二叉变多插
2.一个结点里面存多行的值,也就是一个结点里面有多个关键字以及映射的值

🏆2.B树规则

一棵m阶(m>2)的B树,是一棵平衡的M路平衡搜索树,可以是空树或者满足一下性质:

  1. 根节点至少有两个孩子
  2. 每个分支节点都包含k-1个关键字和k个孩子,其中ceil(m/2) ≤ k ≤ m ceil是向上取整函数
  3. 每个叶子节点都包含k-1个关键字,其中 ceil(m/2) ≤ k ≤ m
  4. 所有的叶子节点都在同一层
  5. 每个节点中的关键字从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域划分
  6. 每个结点的结构为:(n,A0,K1,A1,K2,A2,… ,Kn,An)其中,Ki(1≤i≤n)为关键字,且Ki<Ki+1(1≤i≤n-1)。Ai(0≤i≤n)为指向子树根结点的指针。且Ai所指子树所有结点中的关键字均小于Ki+1。n为结点中关键字的个数,满足ceil(m/2)-1≤n≤m-1。

假设现在我们的m是10
按照上面的规则,也就是说我们
最少需要4个关键字和5个孩子
最多需要9个关键字和10个孩子

这个节点中的关键字按照从小到大的顺序进行排列
(n,A0,K1,A1,K2,A2,… ,Kn,An)
K1<K2<K3<…<Kn
A0节点中的值<K1<A1节点中的值<K2<……也就是说,如果比K1小,我们就在A0节点中继续寻找,如果比K2小比K1大,我们就在A1节点中进行查找。

B树本质是一个多叉搜索树。

💎二、B树插入

🏆1.插入过程

假设M=3。
也就是最少存1个关键字,最多两个关键字,最少2个孩子,最多3个孩子
这里是我们的数据:{53, 139, 75, 49, 145, 36, 101}

首先我们将53,139和75插入
(我们这里多开一个空间,便于我们的插入。否则我们的第m个元素插入的时候,也就是我们刚刚好越界的时候,我们就不知道插入在哪里,我们可能还要分情况进行讨论,这样就非常麻烦。多开辟一个空间的话,我们就可以先将这第M个元素先插进去,然后再进行分裂操作,就省去了分类讨论)

在这里插入图片描述

关键字的数量等于M,那就是满了,满了就分裂,分裂出一个兄弟(兄弟里面最初始没有值),然后分一半的值给兄弟

在这里插入图片描述

插入49,145

在这里插入图片描述

再插入36的时候,我们左边的结点1就满了,我们又需要进行分裂

在这里插入图片描述

49是我们的中位数。
所以我们将49放入我们的父节点中
(我们的关键字要比我们孩子的数量少一个。现在我们有三个孩子和两个关键字)

在这里插入图片描述

在这里插入图片描述

最右边的子树满了,进行持续分裂

在这里插入图片描述

B树天然平衡
因为它是向右和向上生长的
新插入的结点一定是在叶子插入的。叶子没有孩子,所以不会影响孩子和关键字的关系(孩子比关键字多一个)
叶子结点满了,就分裂出一个兄弟,提取中位数,向父亲插入一个值和一个孩子。
根节点分裂才会增加一层。

假设M=1024,那么一个4层的M路的B树可以存多少个值呢?
如果这棵树是全满的情况下
第一层1023个关键字,1024个孩子
第二层10241023个关键字(上一层的每一个孩子也就是这一层的每一个结点都有1023个关键字),10241024个孩子
第三层102410241023个关键字(上一层的每一个孩子也就是这一层的每一个结点都有1023个关键字),102410241024个孩子
第三层1024102410241023个关键字(上一层的每一个孩子也就是这一层的每一个结点都有1023个关键字),1024102410241024个孩子

最差的情况,最空的情况:
第一层只有1个关键字,2个孩子
第二层有2 * 512个关键字,大概1000个关键字,1000个孩子
第三层大概1000 * 512个关键字,1000 * 512个孩子
第四层大概50w * 512个关键字,约等于2.5亿个关键字

🏆2.代码实现

template<class K, size_t M>
struct BTreeNode {

    // 原本key是M-1个大小空间
    // 原本孩子是M个大小空间
    // 为了方便插入以后再分裂,多给一个空间
    K _keys[M];
    BTreeNode<K, M>* _subs[M + 1];
    BTreeNode<K, M>* _parent;
    size_t _n; // 记录实际存储关键字

    //初始化构造函数
    BTreeNode() {
        for (size_t i = 0; i < M; ++i) {
            _keys[i] = K();//缺省值
            _subs[i] = nullptr;
        }

        _subs[M] = nullptr;
        _parent = nullptr;
        _n = 0;
    }
};

// 数据是存在磁盘,K是磁盘地址,是M路的搜索树,我们的M是不确定的
template<class K, size_t M>
class BTree {
    typedef BTreeNode<K, M> Node;
public:
    //返回这个节点和下标
    pair<Node*, int> Find(const K& key) {
        Node* parent = nullptr;
        Node* cur = _root;

        while (cur) {
            // 在一个节点查找
            size_t i = 0;
            while (i < cur->_n) {
                if (key < cur->_keys[i]) {
                    break;
                }
                else if (key > cur->_keys[i]) {
                    ++i;
                }
                //找到了就返回这个节点
                else {
                    return make_pair(cur, i);
                }
            }

            // 往孩子去跳
            // 在往下一层跳之前先将当前的结点给parent
            parent = cur;
            cur = cur->_subs[i];
        }

        //找不到
        return make_pair(parent, -1);
    }

    void InsertKey(Node* node, const K& key, Node* child) {
        int end = node->_n - 1;
        while (end >= 0) {
            if (key < node->_keys[end]) {
                // 挪动key和他的右孩子
                node->_keys[end + 1] = node->_keys[end];
                node->_subs[end + 2] = node->_subs[end + 1];
                --end;
            }
            else {
                break;
            }
        }

        node->_keys[end + 1] = key;
        node->_subs[end + 2] = child;
        if (child) {
            child->_parent = node;
        }

        node->_n++;
    }

    //插入
    bool Insert(const K& key) {
        //第一次插入
        if (_root == nullptr) {
            //如果我们整颗树一个结点都没有
            _root = new Node;
            //将我们的第一个关键字传入
            _root->_keys[0] = key;
            _root->_n++;

            return true;
        }

        // key已经存在,不允许插入
        pair<Node*, int> ret = Find(key);
        if (ret.second >= 0) {
            return false;
        }

        // 如果没有找到,find顺便带回了要插入的那个叶子节点

        // 循环每次往cur插入 newkey和child
        Node* parent = ret.first;
        K newKey = key;
        Node* child = nullptr;
        while (1) {
            InsertKey(parent, newKey, child);
            // 满了就要分裂
            // 没有满,插入就结束
            if (parent->_n < M) {
                return true;
            }
            else {
                size_t mid = M / 2;
                // 分裂一半[mid+1, M-1]给兄弟
                Node* brother = new Node;
                size_t j = 0;
                size_t i = mid + 1;
                for (; i <= M - 1; ++i) {
                    // 分裂拷贝key和key的左孩子
                    brother->_keys[j] = parent->_keys[i];
                    brother->_subs[j] = parent->_subs[i];
                    if (parent->_subs[i]) {
                        parent->_subs[i]->_parent = brother;
                    }
                    ++j;

                    // 拷走重置一下方便观察
                    parent->_keys[i] = K();
                    parent->_subs[i] = nullptr;
                }

                // 还有最后一个右孩子拷给
                brother->_subs[j] = parent->_subs[i];
                if (parent->_subs[i]) {
                    parent->_subs[i]->_parent = brother;
                }
                parent->_subs[i] = nullptr;

                brother->_n = j;
                parent->_n -= (brother->_n + 1);

                K midKey = parent->_keys[mid];
                parent->_keys[mid] = K();


                // 说明刚刚分裂是根节点
                if (parent->_parent == nullptr) {
                    //创建一个新的父节点
                    _root = new Node;
                    _root->_keys[0] = midKey;
                    _root->_subs[0] = parent;
                    _root->_subs[1] = brother;
                    _root->_n = 1;

                    parent->_parent = _root;
                    brother->_parent = _root;
                    break;
                }
                else {
                    // 转换成往parent->parent 去插入parent->[mid] 和 brother
                    newKey = midKey;

                    child = brother;
                    parent = parent->_parent;
                }
            }
        }

        return true;
    }

    void _InOrder(Node* cur) {
        if (cur == nullptr)
            return;

        // 左 根  左 根  ...  右
        size_t i = 0;
        for (; i < cur->_n; ++i) {
            _InOrder(cur->_subs[i]); // 左子树
            cout << cur->_keys[i] << " "; // 根
        }

        _InOrder(cur->_subs[i]); // 最后的那个右子树
    }

    void InOrder() {
        _InOrder(_root);
    }

private:
    Node* _root = nullptr;
};

void TestBtree() {
    int a[] = { 53, 139, 75, 49, 145, 36, 101 };
    BTree<int, 3> t;
    for (auto e : a) {
        t.Insert(e);
    }
    t.InOrder();
}

时间复杂度:

第一层:M
第二层:M * M
第三层:M * M * M
第四层:M * M * M * M

N=M+M2 +M3 +M4 +……+Mh
h约等于
l o g M N log{M}^{N} logMN

💎三、B+树

🏆1.概念

1.分支节点的子树指针与关键字个数相同。(就相当于是取消掉了原先B树每个结点的最左边的那个孩子)
2.分支节点的子树指针p[i]指向关键字值大小在[k[i],k[i+1])区间之间
3.所有叶子节点增加一个链接指针链接在一起
4.所有关键字及其映射数据都在叶子节点出现】

5.节点的关键字数量是[1, M],非根节点关键字数量为[M/2,M]

6.分支节点跟叶子结点有重复的值,分支节点存的是叶子结点的索引
7.父亲中存的是孩子结点中的最小值做索引
8.分支节点可以只存key,叶子结点存key/value
在这里插入图片描述

🏆2.分裂过程

假设这是一棵M == 3的B+树,然后我们B+树要插入的数据是
{53,139,75,49,145,36,101,150,155};

1.依次插入53 139 75

在这里插入图片描述

2.插入19时发生裂变

在这里插入图片描述

在这里插入图片描述

3.插入146和36

在这里插入图片描述

4.插入101的时候发生第二次分裂

 [

在这里插入图片描述

5.插入150,插入155的时候发生连续的两次分裂

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

B+树的插入过程根B树是基本类似的,区别在于第一次插入的时候需要插入两层节点,一层做分支,一层做根,后面一样往叶子去插入,插入满了以后,分一半给兄弟,转换成往父亲插入一个key和一个孩子,孩子就是兄弟,key为兄弟结点的第一个最小值的key

总结:
1.简化孩子比关键字多一个的规则,变成相等。
2.所有值都在叶子上,方便便利查找所有值。

💎四、B*树

🏆1.概念

在这里插入图片描述

  • B树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2);
  • B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了)。
  • 如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针。 所以,B * 树分配新结点的概率比B+树要低,空间使用率更高。
  • B*树主要就是节省空间。

💎五、B树系类应用

B树系列优点:

在内存中做内查找的话和哈希、平衡搜索树对比,单纯论树的高度,搜索效率而言,B树更好

B树系列缺点:

1.空间利用率低,消耗高
2.插入和删除数据、分裂和合并节点,那么必然挪动数据。
3.虽然高度更低,但是在内存中而言,跟哈希和平衡搜索树还是在一个量级的
结论:实质上B树系列在内存中体现不出优势。

B树系列的应用:数据库中的引擎MyISAM或者InnoDB

🏆1.MyISAM

MyISAM引擎是MySQL5.5.8版本之前默认的存储引擎,不支持事务,支持全文检索,使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址

在这里插入图片描述

yISAM中索引检索的算法为首先按 照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为 地址,读取相应数据记录。

🏆2.InnoDB

InnoDB存储引擎支持事务,InnoDB支持B+树索引、全文索引、哈希索引。InnoDB索引,表数据文件本身就是按B+Tree组织的一个索 引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。

在这里插入图片描述

先用name,name对应主键id,再用主键id再去搜索一次,也就是说他用索引查找需要查找两次

B树节点数据都在磁盘文件中。访问节点都是IO行为,只是他们会热数据缓存到Cache中


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

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

相关文章

高等数学(第七版)同济大学 习题11-3 (前7题)个人解答

高等数学&#xff08;第七版&#xff09;同济大学 习题11-3&#xff08;前7题&#xff09; 函数作图软件&#xff1a;Mathematica 1.计算下列曲线积分&#xff0c;并验证格林公式的正确性&#xff1a;\begin{aligned}&1. \ 计算下列曲线积分&#xff0c;并验证格林公式的正…

PyTorch深度学习快速入门教程

PyTorch深度学习快速入门教程1、Pytorch加载数据2、Tensorbord的使用3、Transforms的使用4、常见的Transforms5、torchvision中的数据集使用6、DataLoader的使用7、神经网络的基本骨架—nn.module8、卷积操作9、神经网络—卷积层10、神经网络—池化层的使用11、神经网络—非线性…

靴子落地!Mobileye正式启动4D成像雷达量产进程

4D毫米波雷达赛道正在变得越来越拥挤。 在传统雷达时代&#xff0c;全球主要的市场参与者屈指可数&#xff0c;博世、大陆、安波福、海拉等少数几家巨头几乎垄断前装市场。如今&#xff0c;随着4D时代的开启&#xff0c;越来越多的新进入者希望能够实现换道超车&#xff0c;这…

Jar 组件自动化风险监测和升级实践

背景 以 Xstream、Jackson、Fasjson 等为代表的 Jar 组件高危漏洞层出不穷&#xff0c;安全组每年 N 次推动业务线进行第三方 Jar 组件升级&#xff0c;每次升级动辄涉及成百上千个应用服务&#xff0c;给双方都带来了沉重的负担。为了降低安全组在 Jar 组件升级期间的工作量&…

JS 如何利用浏览器的 cookie 保存用户名

前言 浏览器的cookie可以用来存储一些少量的网站信息,比如登录的用户名,用于提高用户体验非常有帮助 有的一些网站在第一次登录后,在指定的时间范围内容,下次在打开网站,再次登录时,不用每次都重新输入用户名的 或在做一些购物车效果时,也可以使用cookie,保持一个状态持续多…

【数据结构与算法——C语言版】3. 二分查找

前言 本文将介绍在线性表查找中非常常用的一种查找算法——二分法&#xff0c;先介绍二分查找法的核心思路&#xff0c;然后进行代码讲解&#xff0c;最终给出二分查找法的时/空复杂度&#xff0c;并比较其和上篇文章【数据结构与算法——C语言版】2. 数组介绍的顺序查找的区别…

神经网络漫谈(一)

神经网络漫谈(一) 发表时间&#xff1a; 2023年1月6日创作地点&#xff1a;湖北省武汉市作者&#xff1a;ixy_com&Bill Kromydas封面图片来源&#xff1a;Towards Data Science 1、背景 基本概念&#xff1a;神经网络&#xff0c;也称为人工神经网络 (ANN) 或模拟神经…

基础数据结构——二叉树

目录 一、二叉树性质 1、满二叉树、完全二叉树 2、平衡二叉树 3、不平衡二叉树 二、二叉树的存储 1、普通做法 2、竞赛做法 三、二叉树的遍历 1、宽度优先遍历 2、深度优先遍历 &#xff08;1&#xff09;先&#xff08;根&#xff09;序遍历 &#xff08;2&#x…

【java中的集合框架】学习接触java中的集合,走上学习数据结构道路

前言&#xff1a; 大家好&#xff0c;我是良辰呀&#x1f3eb;&#x1f3eb;&#x1f3eb;&#xff0c;从今天开始&#xff0c;我们一起来探索数据结构的知识海洋。期待与大家结伴同行&#xff0c;gogogo。&#x1f36c;&#x1f36c;&#x1f36c; &#x1f9d1;个人主页&…

【自学C++】C++命名空间

C命名空间 C命名空间教程 C 中的命名空间实际上就是一个由程序设计者命名的内存区域&#xff0c;程序设计者可以根据需要指定一些有名字的空间域&#xff0c;把一些全局实体分别放在各个命名空间中&#xff0c;从而与其他全局实体分隔开来。 命名空间是 ANSI C 引入的可以由…

前端入门笔记 03 —— Web(html CSS)布局

常用布局 包含两个定义&#xff1a; 尺寸 定位 定义通过CSS拾取网页元素&#xff0c;控制他们控制普通文档流&#xff0c;周边元素&#xff0c;父容器&#xff0c;浏览器窗口 覆盖默认布局行为盒子模型普通文档流 &#xff08;左到右&#xff0c;上到下&#xff09; 块级元素…

2022年中国数字化十大转型趋势

推动数字化发展既是数字时代构筑竞争新优势的战略选择&#xff0c;也是加快构建“双循环”新发展格局和打造高质量发展新引擎的现实需要。我国高度重视数字化发展&#xff0c;不断完善政策措施&#xff0c;着力推动数字化转型。从行业发展看&#xff0c;构建以数据为驱动、以客…

Redis(一)

Nosql 即 Not-Only SQL&#xff08; 泛指非关系型的数据库&#xff09;&#xff0c;作为关系型数据库的补充。 Nosql 作用&#xff1a;应对基于海量用户和海量数据前提下的数据处理问题。 特征 降低磁盘IO次数&#xff0c;越低越好 —— 内存存储 去除数据间关系&#xff…

网络技术基础

theme: qklhk-chocolate 网络技术基础 一、IP地址基础 IP地址是指在网络中用于标识发送或接收数据报文设备的唯一的逻辑地址。 IP地址的主要作用&#xff1a; 标识主机或网络设备&#xff08;标识其网络接口&#xff0c;提供其在网络中的位置&#xff09;网络寻址 •在IP网…

安装pytorch搭配cuda使用

问题 深度学习程序&#xff0c;在服务器运行&#xff0c;需要借助GPU加速。为了检测是否开启了GPU加速&#xff0c;采用以下代码&#xff1a; ~python >> import torch >> torch.cuda.is_available() >> false #说明没有使用GPU加速安装过程 安装老版本的…

如何做好美颜sdk与直播平台的适配?

美颜sdk作为目前社交视频拍摄平台用户的刚需&#xff0c;在近几年可谓是名声大噪&#xff0c;无论是强大的美颜功能还是多元化的趣味拍摄方案都让用户们“爱不释手”&#xff0c;平台自然也是看中了这一点&#xff0c;纷纷为自己平台接入美颜工具。但是&#xff0c;美颜sdk作为…

免费视频格式转换软件,6大免费视频转换器推荐

看到大多数人拥有电脑、智能电视&#xff0c;尤其是移动设备&#xff0c;这一代人并不奇怪。在线观看电影和视频是最常见的消磨时间的娱乐方式之一。能够通过网络观看视频是件好事。有些人还喜欢下载它以供离线观看&#xff0c;因为您并非一直都在使用 Wi-Fi。有时&#xff0c;…

C++——异常

文章目录1.C语言传统的处理错误的方式2. C异常概念3. 异常的使用3.1 异常的抛出和捕获3.2 异常的重新抛出3.3异常安全3.4 异常规范4.自定义异常体系5.C标准库的异常体系6.异常的优缺点6.1 C异常的优点&#xff1a;6.2 C异常的缺点&#xff1a;1.C语言传统的处理错误的方式 传统…

Codeforces Round #842 (Div. 2)(A~D)

A. Greatest Convex给出数字k&#xff0c;输出最大的x&#xff0c;使得x满足大于等于1小于k&#xff0c;且x! (x - 1)!是k的倍数。思路&#xff1a;提取公因式得到&#xff0c;(x 1) * (x - 1)!&#xff0c;由题意知&#xff0c;x 1可以是k&#xff0c;故x最大是k - 1且一定…

如何制作网站?不知道这个诀窍你的网站等于白做

如何制作网站?不知道这个诀窍你的网站等于白做!#外贸 #独立站 #跨境电商 #网站优化 #网站建设 做每一个网页之前&#xff0c;先确定好网页里面的元素&#xff0c;你知道客户是谁&#xff0c;他目前生意做什么体量&#xff0c;主要做什么市场&#xff1f; 我在根据我拿到的这…