数据结构 B树/B+树 基本概念及操作(自用,复习)

B树

B树的基本概念

一颗m阶B树或为空树,或为满足以下条件的树:

1.每个节点至多用有m个子树,即至多有m-1个关键字。

2.若根节点不是叶子节点(树不止有一个节点),那么根节点至少有2棵子树,即至多有1一个关键字。(如果不止有根节点一个节点,那么根节点的子树起码要有一个关键字,而我们要知道单一关键字对应两颗子树。)

3.除去根节点外的所有非叶节点至少有\left \lceil m/2 \right \rceil(向上取整)个子树,那么就至少有\left \lceil m/2 \right \rceil-1(向上取整)个关键字。

我们可以想象,每个关键字Key,被两个Point裹挟,也可以认为是关键字K将一个大范围分割成了两半。可知P一定会比K多一个。

且遵循K0<K1<K2<...Kn;以及左子树所有关键字<根节点所有关键字<右子树所有关键字的基本原则。

假设说“正无穷”到“负无穷”这个【大范围】,我挑选4作为关键字Key节点,那么衍生而出诞生了两个Point节点将Key围住,也就有了负无穷到4,以及4到正无穷这两个分割之后的【小范围】(子树)

4.所有叶节点必须出现在同一层次,这些节点不包含信息,一般被视为“失败节点”,指向他们的指针为空。(可以得知,任何节点的所有子树高度都相等) 

根据以上概念可知,若B树有n个关键字,那么它所具有的叶节点(失败节点)数量为n+1

B树的查找

B树的查找包含两个操作:

1.在B树中寻找节点。

2.在节点内找关键字。

具体表现为:

从根节点开始开始,假设我们要查找的值为Search(简称S),首先将S与根节点的关键字K比对,如果与节点内的关键字匹配,则找到。

若是没有找到匹配的K,则尝试寻找:最小关键字K0(若S<K0),最大关键字Kn(若S>Kn),或者两个点Ki与Ki+1(若Ki<S<Ki+1)。

根据上面的判断,选择合适的Point指向的子树节点,进行下一轮的关键字比对。

直至找到匹配的关键字节点,或者找到叶节点/“失败节点”。

B树的高度

首先我们定义B树的高度不包含“失败节点”(有些教材中带)

若n>=1,则对任一颗包含n个关键字,高度为h,阶数为m的B树:

1.如果每个节点包含的关键字个数达到最多,则容纳相同关键字个数B树的高度就达到了最小。(没有空余位置,最拥挤的方案,h最小),因为m阶B树的节点最多拥有m个子树,那么节点的关键字个数最多为m-1。

所以一颗高度h的m阶B树中关键字个数应该满足:

n<=(m-1)*(1+m+m^2……m^h-1)=m^h-1(运用等比数列计算)

最后运用纯数学知识计算一下,就能得出h>=log m (n+1)

(m-1):每个节点最多蕴含的关键字个数    Key Per Point简称kpp

(1+m+m^2……m^h-1):整个B树最多蕴含的节点数    Point Count简称pc

Nmax=kpp*pc(并没有叫做kpp与pc的专业术语,单纯为了好理解恶搞一下)

2.若是让每层的关键字个数最少,则B树的高度也会达到最大。

根据B树基本概念,第一层至少有一个结点(内含一个K,两个P),第二层至少有两个结点。除了第一层以外,每层结点需要遵循至少有\left \lceil m/2 \right \rceil(向上取整数)个子树的原则。

于是第一层有1个节点,第二层有2个节点,第三层有2*\left \lceil m/2 \right \rceil,第四层有2*\left \lceil m/2 \right \rceil^2……以此类推,第h+1层有2*\left \lceil m/2 \right \rceil^(h-1)个节点,并且都是叶节点/“失败节点”

我们之前得出过n个关键字的B树,有n+1个叶节点/“失败节点”的结论,现在我们可以用它来定界。

n+1>=2(\left \lceil m/2 \right \rceil)^(h-1)     

即h<=log \left \lceil m/2 \right \rceil((n+1)/2)+1

B树的插入,删除

B树的插入

B树插入的位置,是终端节点(最底层的非叶子节点,也就是叶子节点上一层的节点),但是直接插入过后或许会使整棵树不再满足B树的基本定义要求,所以一般插入过后还要进行调整。

B树插入的步骤:

1.定位插入:利用B树查找算法,找出插入关键字的位置(将K插入指向失败节点的Point位置)

2.判定调整:在插入关键字后,结点关键字的数量会发生变化,如果插入后的数量小于m,那么插入成功;如果插入之后关键字的数量大于m-1,发生溢出,则需要进行分裂调整

分裂:对于需要需要分裂的结点,首先要创造一个新的结点,以原结点第\left \lceil m/2 \right \rceil个关键字为界限,左半部分的关键字留在原结点,右半部分的关键字划入刚刚创建的新结点。最后再将作为界限的第\left \lceil m/2 \right \rceil个关键字,移动到原结点的父节点。

注意:分裂操作或许会使原结点的父节点发生溢出,届时对父结点继续进行分裂操作,直至整棵树再次满足B树的基本定义。

B树的删除

B树的删除操作同样可能导致B树不满足基本定义。

我们首先判断需要删除的关键字,是否为终端结点。

1.非终端节点

需要删除的关键字K,需要其直接前驱(或直接后继)K'顶替。

直接前驱关键字,一般为最靠近K的左子树中,最右下角的关键字(一定位于终端节点);同理,直接后继关键字为最靠近K的右子树中,最左下角的关键字(也必然位于终端节点)。

完成该删除操作之后,一开始被删除的K关键字,被K’顶替,而K‘也从原本所在的终端节点中被删除。

非终端节点的关键字K没了,但有前驱/后继关键字K’填上窟窿,删了一个,又来一个,所以该节点的关键字数量并未发生改变。可是终端节点的K‘没了就是真的没了,K’本来所在的结点,关键字数量实打实的少了一个,如果不再满足B树基本定义的话,需要调整。)

ps:可以得出一个结论,就算我们想要删除的关键字位于非终端结点,最终也可以归结于针对终端节点上关键字的删除。

2.终端节点

如果需要删除的关键字位于终端节点,那么需要分以下三种情况讨论:

1.如果原结点中关键字的数量大于等于\left \lceil m/2 \right \rceil那么就算删掉一个关键字,节点内关键字数量依然满足B树定义的n>=\left \lceil m/2 \right \rceil-1的要求,所以直接删掉就是了。

2.如果原结点内的关键字数量=\left \lceil m/2 \right \rceil-1,那么在删除一个关键字,就不满足B树的定义了,需要进一步调整。具体方法为跟富裕的兄弟借,如果原结点的左/右兄弟节点中,有结点的关键字数量>=\left \lceil m/2 \right \rceil,那么需要将指向原结点,与兄弟节点的父亲节点当中的Point,其中间夹住的关键字Key转移到原结点中,然后从兄弟节点中拿走一个关键字,填补父亲节点的Key(父子换位法)

(原结点删除的关键字窟窿,从父亲节点中抽出来一个补上,然后父亲节点的窟窿,再用富裕的兄弟节点多出来的关键字补上,只有富哥们受伤的世界完成了。父亲变儿子,兄弟变父亲,最熟悉的一集。)

3.如果连需要删除关键字的原结点,和其兄弟节点的关键字数量都是(\left \lceil m/2 \right \rceil-1),兄弟几个家里全都揭不开锅,没有多余的关键字填补窟窿,那么就要采取合并的策略。

将关键字删除后,与左(右)兄弟节点及其双亲结点中的关键字进行合并(双亲中的关键字,就是指向两兄弟的Point,中间夹着的那个关键字Key,两兄弟连带着老父亲一起合并。),合并得出的新结点,顶替原结点

最后得到的新结点一定满足B树定义,但父亲结点却少了一个关键字,这可能会引发新的问题,到时候就按照2.与3.法针对父节点进行调整,直到得到一个满足定义的B树。

ps:如果原结点的父亲结点是根节点,而根节点的数量从1变为0,那么合并得到的新结点就是新的根节点了。

B +树

m阶B+树的基本定义为:

1.每个分支节点最多有m个子树(孩子节点)

2.非叶根节点至少有两颗子树,其他每个分支结点至少有\left \lceil m/2 \right \rceil颗子树

3.节点的子树个数,与关键字个数相等(关键字和子树个数一一对应,这是B +树与B树相比,主要不同的点。虽然两类树中,除了非叶根节点以外的其他结点,子树的上限m和下限\left \lceil m/2 \right \rceil一样,但关键字上限和下限却不同)

4.所有叶节点包含全部关键及其指向相应记录的指针(叶节点一层中,包含整棵树的所有关键字,以及存储关键字信息的地址),叶节点中将关键字按大小顺序排序,并且相邻叶节点按照大小顺序相互链接起来(支持顺序查找)

5.所有分支节点中,只包含其子结点当中最大的关键字,以及指向其子节点的指针。

可以看出B +树相比B树更倾向于“索引”。

B +与B树的主要差异:

1.B +树中,具有n个关键字的结点只有n颗子树;B树中n个关键字的结点,对应n+1颗子树。

2.B +树中,每个结点的关键字个数n的范围是\left \lceil m/2 \right \rceil<=n<=m(非叶根节点:2<=n<=m);而在B树中,每个节点的关键字范围是\left \lceil m/2 \right \rceil-1<=n<=m-1(根节点:1<=n<=m-1)

3.B +树中,叶节点包含了整棵树所有的关键字,非叶节点的关键字也会出现在叶节点,而在B树中,终端节点当中出现的关键字和其他结点中的关键字不会出现重合。

4.B +树中的叶节点包含信息,非叶节点仅仅起到索引作用,当我们在查询的时候,不管是成功还是失败,最终都会找到叶节点所在的层次,查找的时间复杂度相对稳定。而B树中的所有结点都记录有信息(存储地址,指针等),查找到目标关键字后,不会继续向下查询,所以时间复杂度相对不稳定。

5.B +树中,用一个指针指向关键字最小的叶节点,将所有叶节点串成一个相信链表。

  • 35
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,你想了解关于使用FastAPI和Snowy搭建后台管理系统的内容,是吗?那么我会为你提供一些相关的信息。 首先,FastAPI是一个基于Python的高性能Web框架,它使用异步编程方式,支持单线程处理多个请求。相比于其他Python框架,FastAPI的运行速度更快,而且使用简单,易于上手。 而Snowy则是一个基于Vue.js的前端UI框架,它提供了一系列的组件和模板,可以快速搭建出美观、易用的后台管理系统。Snowy的特点是轻量级、易于扩展、响应式设计,很适合用于构建现代化的Web应用程序。 要使用FastAPI和Snowy搭建后台管理系统,你可以按照以下步骤进行: 1. 安装FastAPI和相关依赖 使用pip命令安装FastAPI和uvicorn(一个基于ASGI的Web服务器): ``` pip install fastapi uvicorn ``` 2. 创建FastAPI应用程序 在你的Python项目中创建一个FastAPI应用程序,可以使用类似下面的代码: ```python from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"Hello": "World"} ``` 这个例子创建了一个简单的FastAPI应用程序,其中定义了一个处理HTTP GET请求的路由(/),返回一个JSON对象。 3. 运行FastAPI应用程序 在终端中使用uvicorn命令来运行FastAPI应用程序: ``` uvicorn main:app --reload ``` 这个命令将启动一个Web服务器,监听本地的8000端口,可以通过浏览器访问http://localhost:8000来测试你的应用程序。 4. 集成Snowy前端框架 通过npm命令安装Snowy框架: ``` npm install snowy ``` 在你的Vue.js应用程序中引入Snowy组件: ```javascript import Vue from 'vue' import Snowy from 'snowy' Vue.use(Snowy) ``` 现在你可以使用Snowy提供的组件来构建你的后台管理系统,例如表格、表单、图表等。你可以参考Snowy的文档来学习如何使用这些组件。 5. 开始构建后台管理系统 现在你可以开始使用FastAPI和Snowy来构建你的后台管理系统了。你可以在FastAPI中定义路由和API接口,提供数据服务,而在Snowy中构建前端页面,展示数据和交互。你可以使用Vue.js的数据绑定和事件处理机制,将前端页面和后端数据连接起来。 总之,使用FastAPI和Snowy搭建后台管理系统可以让你快速开发出高性能、易用的Web应用程序。它们都是开源的项目,拥有广泛的社区支持和生态环境,非常适合用于构建现代化的Web应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值