12.多路查找树

12.多路查找树
强烈推介IDEA2020.2破解激活,IntelliJ IDEA 注册码,2020.2 IDEA 激活码

目录

1. 多叉树
2. 2-3树
3. B树
4. B+树
5. 总结

1. 多叉树

二叉树的问题分析:

二叉树的操作效率较高,但在大规模数据存储中,会存在如下问题:
1.构建的二叉树的节点数量会非常多,二叉树的高度过大,这将会导致查找甚至退化成节点内部的线性查找了。
2.并且二叉树的高度过大,会造成I/O操作过于频繁(海量数据一般存储在数据库或文件系统中),进而导致查找效率低下。

多叉树介绍:

由于上述二叉树存在的问题,所以在大规模数据存储的时候,需要通过降低树的高度来提高效率。
在二叉树中,每个节点只有1个数据项,且每个节点最多有两个子节点。但如果允许每个节点可以有更多的数据项和更多的子节点,树的高度自然就降低了,这就是多叉树。

二叉树和多叉树分析详解:

①一般所说的数据结构都是存放在内存之中的,因为CPU的运算速度是非常快的,而存储器当中只有内存的速度能够跟上CPU的处理速度,所以一般情况下数据结构都是存在内存中的。
②另外,硬盘是一个非常慢速的存储设备,内存I/O的速度是比磁盘I/O要快得多的,通常内存的访问速度是纳秒级别的,而磁盘访问的速度是毫秒级别的。读取同样大小的数据,从磁盘中读取花费的时间,是从内存中读取所花费时间的上万倍,甚至几十万倍。
③但是,同样空间大小的内存却又比硬盘要贵得多,像TB级别的数据库不可能全部读出来放到内存中去,过于昂贵,而且也没必要,因为大部分数据是不经常用的,所以通常就需要内存与外存互相配合。
④那么对于二叉树这种数据结构,在海量数据的情况下,树的高度会很高,这就会导致在查找数据时,每访问一个节点,都对应一次磁盘I/O操作,节点过多,频繁读取磁盘,操作效率将非常的低。
⑤所以我们需要通过降低树的高度,来减少查找数据的访问的节点数量, 进而减少I/O操作次数:用m叉树来替换二叉树,m值越大,树的高度也就越低,这样树不用很高就可以标识很大的数据量,检索次数减少,磁盘IO操作减少,查找数据的效率也就提高了。(如果m叉树中的m是100,对一亿个数据构建索引,树的高度也只是3,最多只要3次磁盘I/O就能获取到数据了)。
⑥但是m值也不是越大越好,因为不管是内存中的数据,还是磁盘中的数据,操作系统都是按页(一页大小通常是4KB,这个值可以通过getconfig PAGE_SIZE命令查看)来读取的,一次只会读一页的数据。如果要读取的数据量超过一页的大小,就会触发多次IO操作。所以,我们在选择m大小的时候,要尽量让每个节点的大小等于一个页的大小。读取一个节点,只需要一次磁盘IO操作。

2. 2-3树

2-3树介绍:

2-3树是一种典型的多叉树,也是一种最简单的B树,它有如下特点:
①.2-3树的所有叶子节点都在同一层;
②.2-3树的每个非叶节点都有2个或3个子节点。有2个子节点的节点叫2节点,2节点要么没有子节点,要么同时有两个子节点,2节点只有一个数据项;有3个子节点的节点叫3节点,3节点要么没有子节点,要么同时有3个子节点,3节点必含两个数据项;叶子可以包含一个或两个数据项。
③.2-3树是由2节点和3节点构成的树。
④每个节点的关键字key从左到右保持了从小到大的顺序,两个key之间的子树中所有的key一定大于它的父节点的左key,小于父节点的右key。
在这里插入图片描述

2-3树的构建(插入)过程:

根据2-3树上述的定义,在插入一个关键字构建2-3树的时候按照如下规则:

  1. 如果待插入的节点为2节点:则直接插入即可,节点变为3节点;
    在这里插入图片描述
  2. 如果待插入的节点为3节点:
    2.1. 待插入节点的父节点为2节点:则将新的元素插入到该3节点中,使其成为一个临时的4节点,然后将该节点中的中间元素提升到父节点中,使其父节点成为一个3节点,然后将左右节点分别挂在这个3节点的恰当位置。
    在这里插入图片描述
    2.2. 待插入节点的父节点为3节点:首先和2.1一样拆分插入后的节点,中间元素提升至父节点,但是此时父节点是一个3节点,提升至父节点后,父节点变成了4节点,需要继续将中间元素提升至其父节点,直至遇到一个父节点是2节点,然后将其变为3,不需要继续进行拆分。
    在这里插入图片描述
    2.3. 待插入节点的父节点到根节点均为3节点:按照2.1的拆分步骤,在节点插入新的元素的时候,会一直拆分到根节点,此时根节点变成了一个4节点,需要进一步将根节点拆分为两个2节点,树的高度加1。
    在这里插入图片描述
    例如依次插入{16, 24, 12, 32, 14, 26, 34, 10, 8, 28, 38, 20} 构建的2-3树如下图所示:
    在这里插入图片描述

2-3树节点的删除:

  1. 删除非叶子节点:使用中序遍历下的直接后继节点key来覆盖当前节点key,再删除用来覆盖的后继节点key;
    在这里插入图片描述
  2. 删除叶子节点:
    2.1. 删除的叶子节点是3节点:直接删除;
    在这里插入图片描述
    2.2. 删除的叶子节点是2节点:
     2.2.1. 当前节点的双亲节点是2节点、兄弟节点是3节点:将双亲节点移动到当前位置,再将兄弟节点中最接近当前位置的key移动到双亲节点中;
    在这里插入图片描述
     2.2.2. 当前节点的双亲节点是2节点、兄弟节点也是2节点:先通过移动兄弟节点的中序遍历直接后驱到兄弟节点,以使兄弟节点变为3节点;再进行2.2.1的操作;
    在这里插入图片描述
     2.2.3. 当前节点的双亲节点是3节点:拆分双亲节点使其成为2节点,再将再将双亲节点中最接近的一个拆分key与中孩子合并,将合并后的节点作为当前节点;
    在这里插入图片描述
     2.2.4. 2-3树是一颗满二叉树:将2-3树层树减少,并将兄弟节点合并到双亲节点中,同时将双亲节点的所有兄弟节点合并到双亲节点的双亲节点中,如果生成了4节点,再分解4节点即可。
    在这里插入图片描述

除了2-3树,还有2-3-4树,概念和2-3树类似,只是多了4节点,也是一种B树。

3. B树

B树的介绍:

B-tree树即B树,全称Balance-tree(平衡多路查找树)。有的人将英文短线翻译成了减号,把B-tree翻译成B-树,这容易让人产生误解,会以为B-树是另一种树,实际上,B-树就是指的B树。

B树的定义:

B树的阶:节点的最多子节点个数。比如2-3树的阶是3,2-3-4树的阶是4。
 
一颗 m 阶的B树,或者本身是空树,否则必须满足以下特性:

  1. 树中每个节点至多有m 棵子树;
  2. 若根节点不是叶子结点,则至少有两棵子树;
  3. 除根节点之外的所有非叶子节点至少有p个子节点(⌈m/2⌉ ≤ p ≤ m, ⌈m/2⌉为向上取整);
  4. 所有的非叶子节点中包含下列信息数据:
    (n,A0,K1,A1,K2,A2,…,Kn,An)
    n 表示结点中包含的关键字的个数,取值范围是:⌈m/2⌉-1≤ n ≤m-1。Ki (i 从 1 到 n)为关键字,且 Ki < Ki+1 ;Ai 代表指向子树根结点的指针,且指针 Ai-1 所指的子树中所有结点的关键字都小于 Ki,An 所指子树中所有的结点的关键字都大于 Kn。
    在这里插入图片描述
    例如上图中当前节点中有 4 个关键字,之间的关系为:K1<K2<k3<K4。同时对于 A0 指针指向的子树中的所有关键字来说,其值都要比 K1 小;而 A1 指向的子树中的所有的关键字的值,都比 K1 大,但是都要比 K2 小。
  5. 所有的叶子节点都出现在同一层次,不携带信息,指向这些结点的指针都为 NULL;(可以看作是外部节点或查找失败的节点,实际上这些节点不存在,指向这些结点的指针为空)
    在这里插入图片描述
    例如图上图所示就是一棵 4 阶的 B-树,这棵树的深度为 4 。

B树节点的操作:

1.B树插入节点的方式与2-3树相似,只不过在B树中非叶子节点中包含关键字的个数的范围是[⌈m/2⌉-1,m-1],所以如果在插入新的数据元素时,如果该节点中的关键字个数没有超过 m-1,则直接插入成功,否则就该节点进行拆分处理。
2.删除操作同理。

B树的应用:

使用B-tree结构可以显著减少定位记录时所经历的中间过程,从而加快存取速度。该数据结构一般用于数据库的索引,综合效率较高。

4. B+树

B+树介绍:

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

  1. 有 n 棵子树的节点中含有 n 个关键字;
    在 B-树中的每个结点关键字个数 n 的取值范围为⌈m/2⌉ -1≤n≤m-1,而在 B+树中每个结点中关键字个数 n 的取值范围为:⌈m/2⌉≤n≤m
  2. 所有的叶子节点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子节点本身依关键字的大小自小而大顺序链接。
  3. 所有的非叶子节点可以看成是索引部分,节点中仅含有其子树根节点中的最大(或最小)关键字。
    在这里插入图片描述
    如上图所示:B+树中含有两个头指针,一个指向整棵树的根结点,另一个指向关键字最小的叶子结点。同时所有的叶子结点依据其关键字的大小自小而大顺序链接,所有的叶子结点构成了一个 sqt 指针为头指针的链表。

B+树小结:

所以,B+树可以进行两种查找运算:一种是利用 sqt 链表做顺序查找,另一种是从树的根节点开始,进行类似于二分查找的查找方式,这也正是B+树的设计思想,快速在链表中定位到要查找的数据。
 
在 B+树中,所有非叶子节点都相当于是叶子节点的索引,而所有的关键字都存放在叶子节点中,所以在从根节点出发做查找操作时,如果非叶子节点上的关键字恰好等于给定值,此时并不算查找完成,而是要继续向下直到叶子节点。B+树的查找操作,无论查找成功与否,每次查找操作都是走了一条从根节点到叶子节点的路径。

B+树的节点操作:

1.在B+树中插入关键字时,需要注意以下几点:

  • 插入的操作全部都在叶子节点上进行,且不能破坏关键字自小而大的顺序;
  • 由于 B+树中各节点中存储的关键字的个数有明确的范围,做插入操作可能会出现节点中关键字个数超过阶数的情况,此时需要将该节点进行“分裂”(同前文2-3树,一般分裂为左⌊M/2⌋,右⌈M/2⌉);
    2.删除操作同理。

5. 总结

由于 B-树、B+树都具有分支多层数少的特点,更多的是用于文件索引系统,所以没有介绍具体地代码实现,只需要了解实现过程即可。

本文来源MrKorbin,由架构君转载发布,观点不代表Java架构师必看的立场,转载请标明来源出处:https://javajgs.com/archives/25274

发表评论