数据结构与算法--B树原理及实现

B树

  • 前几篇文中讨论的数据结构我们都是假设所有的数据都存储在计算机的主存中。可说总要那么海量的数据需要通过个中数据结构去存储,我们不可能有这么多内存区存放这些数据。那么意味着我们需要将他们放磁盘。所以这个时候范问时间复杂度O决定了他是否能适合存储磁盘,大O模型不适用于磁盘存储。游戏规则变了。

  • 在磁盘中操作速度和CPU内存计算速度相差甚远。

    • 例如一台机器每秒可以执行5亿条指令。
    • 磁盘读写操作依赖磁盘的机械运动,磁盘转动,磁针移动,也许磁盘7200RPM的转速,1转大约1/120秒,约8.3ms,我们就估算是8.3
    • 每秒大概120次磁盘访问
    • 但是1分钟7200转比起每秒5亿简直不是一个量级,比值相当于 40W:1
    • 方法是尽量减少磁盘的操作,宁愿大量计算
    • 对于树来说,一个节点存在几个子节点,我们在内存中通过指针寻址,但是在磁盘中需要机械运动寻址
案例
  • 我们通过一个案例来结束典型查找树的执行方式:
    • 假设要访问广东市所有人的出行记录。假设只有1kw项数据。每个关键字32字节(是这个人的名字+唯一id),而一个记录存储这N个信息是256个字节。假设这些1kw数据不能都存储进主内存
    • 更具如上计算,我们
解决方法
  • 不平衡的二叉查找树解决如上问题完全不可取,最坏情况他有线性深度,从而可能需要1kw次磁盘的范问。平均的话一次成功的查找也需要1.38logN次范围,由于log10000000 =24 ,1.38*24 = 32/6 = 5秒。AVL树多少要好一点,1.44logN是最坏情况,平均访问是logN,择优AVL计算平均25次磁盘查询一个数据,4秒左右

  • 要减少磁盘范问次数,比如减到3,4。我们需要更加复杂的程序。因为机器指令相对机械操作相当于不耗时。以上分析我们知道二叉树不可行,减少树层级增加节点分支的方式来解决。

  • 31个节点的完美二叉树有5层,31个节点的5叉树只有3层

  • 一个完全二叉树的高度大概是log2N,而一颗完全M叉树的高度大概是LogMN

  • 我们建立与二叉树类似的M叉查找树,二叉树中我们需要一个key来决定走左树还是右树,M叉树我们需要M-1个key来决定

  • 同时避免M叉树退化成链表,我们需要给一个平衡条件,限制M叉树退化到二叉树,那就是B树如下性质

  • 阶为M的B树是一颗具有下列特性的树:

    • 数据项存储在叶子节点上
    • 非叶子节点存储直到M-1个关键字用来指示搜索方向;关键字i代表子树i+1中最小关键字
    • 数的根节点如果不是叶子节点,那么他子节点个数必须在2~M之间
    • 除了根节点外,所有非叶子节点的子节点个数必须在M/2~M个之间
    • 所有叶子节点都在相同的深度上,并且有L/2和L个数据项目,L个数之后确认
  • 如下图5阶B树的案例,所有非叶子节点的儿子树都在3~5之间(5/2 ~ 5 取整 3-5),那么他们有2-4个关键字;根可能只有2个子节点。这里我们L=5,所有叶子节点的数据项则是(5/2 ~5 取整 3-5 之间)

    • 要求节点半满保证B树不退化到简单二叉树
      在这里插入图片描述
B树结构分析
  • 我们根据所存储的项的大小选择M与L的大小,还是上面的案例,我们假设一个区块的磁盘大小是8192Byte,
  • 每个关键字32Byte,在一颗M阶B数中,有M-1个关键字那么总数是32(M-1),再加上M个分支(指针int型4Byte),总数则为32(M-1)+4M = 36M-32
  • 那么一个片区8192可以存储的最大节点数据是8192= (36M-32) , M = 228阶,也就是我们每一层有228个分支
  • 以上案例中条件说一个记录256Byte,8192/256 = 32 ,一个片区可以存储32个记录,我们选择L=32,这样一个子节点中所有数据可以在同一个片区(尽量一次磁盘操作可以读完)
  • 这样我们保证了每个叶子节点有16~32个数据记录以及每个内部节点至少有228/2 = 114 中分支方式
  • 那么1kw个数据最多存储的叶子节点个数 10000000/16 = 625000 个叶子节点中
  • 层级最少情况,每一层都是满分支(228),625000/228 = 224 /228 = 1,最少的情况第二次就是叶子节点
  • 层级最多情况,每一层都是半分支(228/2=114),625000/114 = 5482.4/114=48/1154 = 1 ,最多情况第四层是叶子节点
  • 如上分析,最坏情况访问次数近似log(m/2)N给出
构造B树–添加节点,删除节点
插入节点
  • 我们按照上面图片中的案例,需要将57 插入到图中的B树,沿着树向下查找他不在树中,我们把它作为第五项数据添加到第对应的叶子节点中如下图:
    在这里插入图片描述
方法一
  • 如上更新数据分为三步:
    • 第一遍历B树,找到插入数据的对应位置
    • 读取树叶节点中数据并且重排树叶节点中数据
    • 将重排的数据写入树叶节点
  • 与磁盘的操作相比,这些逻辑排列时间几乎可以忽略不计,以上是简单的模式,因为改树叶节点并没有被装满,现在我们在插入55 到上图中。
    • 55要插入的那个叶子节点已经满,有L+1项数据,我们需要将他分成两个叶子节点,这两叶子节点能保证所有需要的记录的最小个数,每一个叶子节点3个项(需要两次磁盘范问)
    • 接着我们需要更新父节点,将最小的值添加到父节点中(需要一次写入的磁盘操作),这种方式是更具上面L/2的规则执行
    • 虽然分离节点是耗时的,至少需要2次磁盘操作,但是很少发生,比如我们上面L=32 时候每次节点分裂时候都有16 或者17 项数据的两片叶子节点,那么我们需要之后的17次添加操作才会有一次分裂

在这里插入图片描述

  • 如上图情况,当一个非叶子节点分裂,父节点得到一个新的子节点,父节点也需要增加一个项,如果父节点此时也达到分裂阀值,则需要继续向上分裂直到达到根节点。如果分裂根就得到两个根,这显然不现实,我们则创建一个新根,这个根以分裂得到的两个新根为子节点,所以我们的根节点的子节点的个数设置在2~M个是有这个原因的。
  • 这种方式也是B树增加高度的唯一方式
方法二
  • 在相邻节点有空间时候,将一个子节点交给邻节点,例如将上图中29 节点插入到B树中,我们可以把32移到 下一片树叶而腾出一个空间来,这种方法要求对父节点进行修改,因为这些关键字也会受影响,他趋向于是的节点达到满节点,更加节省空间。
删除节点
  • 通过查找到要删除的项并在找到后删除他来执行删除操作,问题在于,如果被删元素的总元素个数已经低于最小值(L/2),那么删除后他的项目就不符合B树的要求,我们可以通过在邻节点借一个节点来矫正这种情况,如果邻节点也达到最小值(L/2),那么我们就合并这两个叶子节点结合成一个新的满叶节点。
  • 这种操作意味着父节点是去一个儿子,如果失去儿子节点后又引发儿子节点数低于下限,我们继续使用相同的策略,一直到根节点,如果这个过程使得根只剩下一个子节点,那么删除这个根节点,让原儿子节点当现在B树的新根节点。
  • 这种方式是B树降低高度的唯一方式。
  • 如下案例,我们还是依照上图中的B数,删除99 项的节点,由于叶子节点只有两个,而其邻节点以及是最小值3 个了,因此,我们将这两个叶子节点合并成有5项数据的一片新的叶子节点。结果他的父节点只有两个子节点,此时父节点继续上一个步骤,从邻节点领养,邻节点有4个子节点,最终使用双方都得到3个子节点,如下图:
    在这里插入图片描述

上一篇:数据结构与算法–AVL树原理及实现
下一篇:数据结构与算法–图论,最短路算法,拓扑排序算法

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

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

相关文章

[C++STL]C++实现priority_queue容器适配器

代码如下: #pragma once #include <iostream> #include <vector> using namespace std;template<typename T> struct Less {bool operator()(const T &a, const T &b){return a < b;} };template<typename T> struct Greater {bool operat…

为什么要用内插字符串代替string.format

知道为什么要用内插字符串&#xff0c;只有踩过坑的人才能明白&#xff0c;如果你曾今使用string.format超5个以上占位符&#xff0c;那其中的痛苦我想你肯定是能够共鸣的。一&#xff1a;痛苦经历先上一段曾今写过的一段代码&#xff0c;大家来体会一下&#xff1a;LogHelper.…

SpringCloud Alibaba 框架下公司架构图

上一篇&#xff1a;Docker容器实战思维 下一篇&#xff1a;分布式事务理论模型

C++ 实现堆

代码如下(小根堆): #include <iostream> #include <assert.h> using namespace std;//小根堆,堆排序从大到小排 class Heap { public:Heap():data(nullptr), size(0), capacity(0) {}~Heap(){delete[] data;data nullptr;size 0;capacity 0;}/*void Swap(int *…

iPhone上运行Linux也要来了

FOSSBRTES 报道称&#xff0c;用户很快将可以通过双启动功能&#xff0c;像在 Android 设备上那样在 iPhone 上运行 Linux。目前&#xff0c;iOS 越狱极客、开发人员 Raffaele 以及 mcg29 已在其 Github 页面上发布了详细说明&#xff0c;详细介绍了如何双启动 64 位 iOS 设备。…

数据结构与算法--图论,最短路算法,拓扑排序算法

图论若干定义 图&#xff08;graph&#xff09;G&#xff08;V,E&#xff09;由定点vertex的集合V&#xff0c; 和边edge的集合E组成。每一条边都是一个点对点&#xff08;v&#xff0c;w&#xff09;&#xff0c;其中 v,w 属于V集合的子集 如果点对点 是有序的&#xff0c;那…

谁说.NET不适合搞大数据、机器学习和人工智能

SciSharp StackSciSharp STACK: https://scisharp.github.io/SciSharp/基于.NET的开源生态系统&#xff0c;用于数据科学、机器学习和AI。SciSharp将所有主要的ML/AI框架从Python引入.NET.特点为.NET开发者.NET开发者使用他们所了解和喜爱的工具可以最高效的工作。我们的使命是…

C++ 泛型编程 实现红黑树RBTree

代码如下: #include <iostream> #include <ctime> using namespace std;enum COLOR {BLACK,RED };template<typename T> struct RBTreeNode {RBTreeNode<T> * _parent;RBTreeNode<T> * _left;RBTreeNode<T> * _right;T _val;COLOR _color…

数据结构与算法--图论-深度优先搜索及其应用

深度优先搜索 深度优先搜索&#xff08;depth-first search&#xff09; 是对先序遍历&#xff08;preorder traversal&#xff09;的推广&#xff0c;我们从某个顶点v开始处理v&#xff0c;然后递归的遍历所有与v邻接顶点。如果这个过程是对一棵树进行&#xff0c;那么&#…

.NET Core技术研究-主机

前一段时间&#xff0c;和大家分享了 ASP.NET Core技术研究-探秘Host主机启动过程但是没有深入说明主机的设计。今天整理了一下主机的一些知识&#xff0c;结合先前的博文&#xff0c;完整地介绍一下.NET Core的主机的设计和构建启动过程。一、什么是主机主机是一个封装了应用资…

数据结构与算法--贪婪算法

贪婪算法 贪婪算法分阶段地工作。在每个阶段&#xff0c;可以认为所做决定是最好的&#xff0c;而不考虑将来的后果。通常这意味着选择的是某个局部最优。这种“当前能获得的最优就拿”的策略是这类算法的名字来源。当算法终止时候&#xff0c;我们希望的到累积的局部最优解就…

[C++STL]C++ 实现map容器和set容器

代码如下: #pragma once #include <iostream> using namespace std;enum COLOR {BLACK, RED };template<class V>//迭代器声明&#xff0c;定义在后面 struct RBTreeIterator;template<typename V> struct RBTreeNode {RBTreeNode<V> * _parent;RBTre…

多角度让你彻底明白yield语法糖的用法和原理及在C#函数式编程中的作用

如果大家读过dapper源码&#xff0c;你会发现这内部有很多方法都用到了yield关键词&#xff0c;那yield到底是用来干嘛的&#xff0c;能不能拿掉&#xff0c;拿掉与不拿掉有多大的差别&#xff0c;首先上一段dapper中精简后的Query方法&#xff0c;先让大家眼见为实。private s…

C++泛型编程实现哈希表(闭散列---线性探测)

代码如下: #include <iostream> #include <vector> using namespace std;enum STATE {EXIST,DELETE,EMPTY };template<typename K,typename V> struct HashNode {pair<K, V> _kv;STATE _state EMPTY; };template<typename K,typename V> class…

哪种开源许可证最适合商业化?

选择最佳开源许可证是为新项目所做的最重要的决定之一。大多数开发者会选用 MIT、BSD 或 Apache 等流行的宽松许可证&#xff08;permissive license&#xff09;。对于商业项目而言&#xff0c;这种选择不错&#xff0c;因为这能减少用户对项目的抵触情绪。当应用于开源项目时…

C++泛型编程实现哈希表(开散列法)

代码如下: #include <iostream> #include <vector> using namespace std;template<typename K> struct HashNode {typedef HashNode<K> Node;K _val;Node * _next;HashNode(const K & val):_val(val),_next(nullptr){} };template<typename K&…

数据结构与算法--分治算法-最大子序列和问题

分治算法 用于设计算法的一种常用技巧–分治算法&#xff08;divide and conquer&#xff09;。分治算法由两部分组成&#xff1a; 分(divide)&#xff1a;递归然后借机较小的问题&#xff08;基础情况除外&#xff09;治(conquer)&#xff1a;然后从子问题的解构建原问题的解…

请把我不会,换成我可以学

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份有位读者跟我说起自己的烦恼&#xff1a;“我到公司已经接近四年了&#xff0c;领导经常让我做一些岗位职责以外的事情。这些东西我都不会&#xff0c;还非让我做。并且一直没有职位上的改变&#xff0c;我怎…

[C++STL]C++实现unordermap容器和unorderset容器

代码如下: #include <iostream> #include <vector> using namespace std;template<typename K,typename V,typename KeyOfValue> class HashTable;//声明template<typename V> struct HashNode {typedef HashNode<V> Node;V _val;Node * _next;…

还不会docker+k8s?2020年,就要面对现实了...

docker的前世今生2010年&#xff0c;几个年轻人&#xff0c;在美国旧金山成立了一家名叫“dotCloud”的公司。这家公司主要提供基于PaaS的云计算技术服务。具体来说&#xff0c;是和LXC有关的容器技术。后来&#xff0c;dotCloud公司将自己的容器技术进行了简化和标准化&#x…