数据结构与算法之多路查找树(2-3树、2-3-4树、B树、B+树)

简介: 二叉树需要加载到内存的,如果二叉树的节点少,没有什么问题,但是如果二叉树的节点很多(比如1亿), 就存在如下问题:问题1:在构建二叉树时,需要多次进行I/O操作(海量数据存在数据库或文件中),节点海量,构建二叉树时,速度有影响.问题2:节点海量,也会造成二叉树的高度很大,会降低操作速度.

常用数据结构与算法实现

以下博客根据B站罗召勇老师视频:数据结构与算法基础-Java版(罗召勇)写的详细笔记


数据结构与算法基础:


数据结构与算法之基础概述


数据结构:


(一)数据结构与算法之数组

(二)数组结构与算法之栈

(三)数据结构与算法之队列

(四)数据结构与算法之链表

(五)数据结构与算法之树结构基础

(六)数据结构与算法之二叉树大全

(七)数据结构与算法之Huffman tree(赫夫曼树 / 霍夫曼树 / 哈夫曼树 / 最优二叉树)

(八)数据结构与算法之多路查找树(2-3树、2-3-4树、B树、B+树)

(九)数据结构与算法之图结构


十大经典算法:


(一)数据结构与算法之冒泡排序(含改进版)

(二)数据结构与算法之选择排序(含改进版)

(三)数据结构与算法之插入排序(含改进版)

(四)数据结构与算法之希尔排序

(五)数据结构与算法之归并排序

(六)数据结构与算法之快速排序

(七)数据结构与算法之堆排序

(八)数据结构与算法之计数排序

(九)数据结构与算法之桶排序

(十)数据结构与算法之基数排序


为什么使用多路查找树

二叉树存在的问题

二叉树需要加载到内存的,如果二叉树的节点少,没有什么问题,但是如果二叉树的节点很多(比如1亿), 就存在如下问题:


问题1:在构建二叉树时,需要多次进行I/O操作(海量数据存在数据库或文件中),节点海量,构建二叉树时,速度有影响.


问题2:节点海量,也会造成二叉树的高度很大,会降低操作速度.

image.png


解决上述问题 —> 多叉树


多路查找树

1、在二叉树中,每个节点有数据项,最多有两个子节点。如果允许每个节点可以有更多的数据项和更多的子节点,就是多叉树(multiway tree)

2、后面我们讲解的"2-3树","2-3-4树"就是多叉树,多叉树通过重新组织节点,减少树的高度,能对二叉树进行优化。

3、举例说明(下面2-3树就是一颗多叉树)

image.png

2-3树

2-3树定义


所有叶子节点都要在同一层

二节点要么有两个子节点,要么没有节点

三节点要么没有节点,要么有三个子节点

不能出现节点不满的情况

image.png

2-3树插入的操作

插入原理:


对于2-3树的插入来说,与平衡二叉树相同,插入操作一定是发生在叶子节点上,并且节点的插入和删除都有可能导致不平衡的情况发生,在插入和删除节点时也是需要动态维持平衡的,但维持平衡的策略和AVL树是不一样的。


AVL树向下添加节点之后通过旋转来恢复平衡,而2-3树是通过节点向上分裂来维持平衡的,也就是说2-3树插入元素的过程中层级是向上增加的,因此不会导致叶子节点不在同一层级的现象发生,也就不需要旋转了。


三种插入情况:


1)对于空树,插入一个2节点即可;


2)插入节点到一个2节点的叶子上。由于本身就只有一个元素,所以只需要将其升级为3节点即可(如:插入3)。

image.png


3)插入节点到一个3节点的叶子上。因为3节点本身最大容量,因此需要拆分,且将树中两元素或者插入元素的三者中选择其一向上移动一层。


分为三种情况:


升级父节点(插入5)

image.png

升级根节点(插入11)

image.png

增加树高度(插入2,从下往上拆)

image.png

2-3树删除的操作

删除原理:2-3树的删除也分为三种情况,与插入相反。


三种删除情况:


1)所删元素位于一个3节点的叶子节点上,直接删除,不会影响树结构(如:删除9)

image.png

2)所删元素位于一个2节点上,直接删除,破坏树结构

image.png


分为四种情况:


此节点双亲也是2节点,且拥有一个3节点的右孩子(如:删除1)

image.png


此节点的双亲是2节点,它右孩子也是2节点(如:删除4)

image.png


此节点的双亲是3节点(如:删除10)

image.png


当前树是一个满二叉树,降低树高(如:删除8)

image.png


3)所删元素位于非叶子的分支节点。此时按树中序遍历得到此元素的前驱或后续元素,补位


两种情况:


分支节点是2节点(如:删除4)

image.png

分支节点是3节点(如:删除12)

image.png

2-3-4树

2-3-4树是2-3树的扩展,包括了 4 节点的使用,一个 4 节点包含小中大三个元素和四个孩子(或没有孩子)


2-3-4树的插入操作

1)如果待插入的节点不是 4 节点,则直接插入即可


2)如果待插入的节点是 4 节点,则先把新节点临时插入进去变成 5 节点,然后对 5 节点进行向上分裂、合并,5 节点分裂成两个 2 节点(5 节点最小的元素、5 节点第二个元素)、1个 3 节点(5 节点后两个元素),然后将分裂之后的第2个 2 节点向上合并到父节点中,然后把父节点作为插入元素之后的当前节点,重复(1)、(2)步骤,直到满足2-3-4树的定义性质

image.png

image.png




2-3-4树的删除操作

删除顺序使1,6,3,4,5,2,9

image.png


B树

B树(BTree)是一种平衡的多路查找树,2-3树和2-3-4树都是B树的特例。


我们把结点最大的孩子树目称为B树的阶,因此,2-3树是3阶B树,2-3-4树是4阶B树

image.png


如下图,比如说要查找7,首先从外存读取得到根节点3,5,8三个元素,发现7不在,但是5、8之间,因此就通过A2再读取外存的2,6,7节点找到结束。

image.png


B树的数据结构为内外存的数据交互准备的。当要处理的数据很大时,无法一次全部装入内存。这时对B树调整,使得B树的阶数与硬盘存储的页面大小相匹配。比如说一棵B树的阶为1001(即1个节点包含1000个关键字),高度为2(从0开始),它可以存储超过10亿个关键字(1001x1001x1000+1001x1000+1000),只要让根节点持久的保留在内存中,那么在这颗树上,寻找某一个关键字至多需要两次硬盘的读取即可。


对于n个关键字的m阶B树,最坏情况查找次数计算

第一层至少1个节点,第二层至少2个节点,由于除根节点外每个分支节点至少有⌈m/2⌉棵子树,则第三层至少有2x⌈m/2⌉个节点。。。这样第k+1层至少有2x(⌈m/2⌉)^(k-1),实际上,k+1层的节点就是叶子节点。若m阶B树有n个关键字,那么当你找到叶子节点,其实也就等于查找不成功的节点为n+1,因此

n+1>=2x(⌈m/2⌉)^(k-1),即

image.png


在含有n个关键字的B树上查找时,从根节点到关键字节点的路径上涉及的节点数不超多


B+树

B+树可以说是B树的升级版,相对于B树来说B+树更充分的利用了节点的空间,让查询速度更加稳定,其速度完全接近于二分法查找。大部分文件系统和数据均采用B+树来实现索引结构。


下图B树,我们要遍历它,假设每个节点都属于硬盘的不同页面,我们为了中序遍历所有的元素,页面2-页面1-页面3-页面1-页面4-页面1-页面5,页面1遍历了3次,而且我们每经过节点遍历时,都会对节点中的元素进行一次遍历

image.png


B+树是应文件系统所需而出的一种B树的变形树,在B树中,每一个元素树中只出现一次,而B+树中,出现在分支节点中的元素会被当做他们在该分支节点位置的中序后继者(叶子节点)中再次列出。另外,每一个叶子节点都会保存一个指向后一叶子节点的指针。

下图就是B+树,灰色关键字,在根节点出现,在叶子节点中再次列出

image.png


一棵m阶的B+树和m阶的B树的差异在于:


有n棵子树的非叶节点中包含有n个关键字(B树中是n-1个关键字),但是每个关键字不保存数据,只用来保存叶子节点相同关键字的索引,所有数据都保存在叶子节点。(此处,对于非叶节点的m颗子树和n个关键字的关系,mysql的索引结构似乎是m=n+1,而不是上面的m=n)

所有的非叶节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。

所有的叶子节点包含全部关键字的信息,及指向含这些关键字所指向的具体磁盘记录的指针data,并且每一个叶子节点带有指向下一个相邻的叶节点指针,形成链表

总结

B树的非叶节点会存储关键字及其对应的数据地址,B+树的非叶节点只存关键字索引,不会存具体的数据地址,因此B+树的一个节点相比B树能存储更多的索引元素,一次性读入内存的需要查找的关键字也就越多,B+树的高度更小,相对IO读写次数就降低了。


B树的查询效率并不稳定,最好的情况只查询一次(根节点),最坏情况是查找到叶子节点,而B+树由于非叶节点不存数据地址,而只是叶子节点中关键字的索引。所有查询都要查找到叶子节点才算命中,查询效率比较稳定。这对于sql语句的优化是非常有帮助的。


B+树所有叶子节点形成有序链表,只需要去遍历叶子节点就可以实现整棵树的遍历。方便数据的范围查询,而B树不支持这样的操作或者说效率太低。


现代数据库和文件系统的索引表大部分是使用B+树来实现的,但并不是全部


相关文章
|
16天前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
53 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
16天前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
39 12
|
16天前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
39 10
|
16天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
40 2
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
91 5
|
2月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
292 9
|
2月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
45 1
|
16天前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
132 77
|
16天前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
37 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
16天前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
39 9

热门文章

最新文章