【复习笔记】B树、B+树

RIOChing

关注

阅读 51

2022-08-18


一、二分查找

当序列分布较为均匀时效率高

int bin_search(vector<int> &nums, int key){
int low = 0;
int high = nums.size() - 1;
while(low <= high){
int mid = (low + high) / 2;
if(nums[mid] < key){
low = mid + 1;
}else if(nums[mid] > key){
high = mid - 1;
}else{
return mid;
}
}
return -1;
}

二、插值查找

当查找的序列非常极端时效率高,比如在序列:1,2,4,6,7,9,11,12,13,999,1000中查找999,若是用二分查找效率就很低了,需要查找3次。若用插值查找就只需要查找1次就行了。当数据非常大且极端时,插值查找的效率就体现得非常明显

int insert_search(vector<int> &nums, int key){
int low = 0;
int high = nums.size() - 1;
while(low <= high){
//因为除法会向下取整,所以mid的计算结果不会越界
int mid = low + (high-low)*(key-nums[low])/(nums[high]-nums[low]);
if(nums[mid] < key){
low = mid + 1;
}else if(nums[mid] > key){
high = mid - 1;
}else{
return mid;
}
}
return -1;
}

三、分块查找

在块内无序,块间有序的顺序表中查找元素。

【复习笔记】B树、B+树_分块


首先需要建立索引表,索引表的每个元素记录每个分块的最大关键字和分块的存储区间。索引表元素数据结构如下:

typedef struct{
ElemType maxValue;//块内最大值
int low;
int high;
}Index;

分块查找算法过程:

  1. 在索引表中确定待查值所属的分块(可顺序,可折半)
  2. 在块内顺序查找

注:对索引表进行折半查找时,若索引表中不包含目标关键字,则折半查找最终停在,要在所指分块中查找

【复习笔记】B树、B+树_结点_04


当对顺序表中的元素进行插入或删除的时候,除了顺序表中元素都要移动以外,还需要修改索引表元素的数据。效率太低,一般的我们可以改进为链式存储,如下:

【复习笔记】B树、B+树_分块_05


插入元素时,只需要挂在最后即可。删除元素时,修改指针即可。

四、B树

【复习笔记】B树、B+树_分块_06


基本概念:

  1. 终端结点:最下层含有实际数据的结点
  2. 叶子结点:最下层表示空值的结点
  3. B树的阶:B树中所有结点的孩子个数的最大值称为B树的阶

一棵m阶B树或为B树,或为满足以下特性的m叉树:

  1. 树中每个结点最多有棵子树,即至多含有关键字
  2. 若根结点不是终端结点,至少有两棵子树(包含在绝对平衡里)
  3. 除了根结点外的所有非叶结点至少有棵子树,即至少含有个关键字。(每个结点尽可能存满,树不会太深)
  4. 所有的叶结点都在同一层次上,这些结点都代表空
  5. 对于任何一个非叶结点,左右子树高度相同,即绝对平衡
  6. 对于任何一个非叶结点中的任何一个元素,左指针所指块包含元素<该元素<右指针所指块包含元素,类似于排序树

【复习笔记】B树、B+树_分块_11

5叉排序树结点的定义

struct Node{
ElemType keys[4]; //最多4个关键字
struct Node* child[5]; //最多5个孩子
int high; //结点中关键字数量
};

含有n个关键字的m叉B树,最小高度和最大高度

【复习笔记】B树、B+树_子树_12

最小高度——让每个结点尽可能满,有个关键字,个分叉,则有:
,因此

最大高度——让每层分叉尽可能少,即根结点只有2个分叉,其他结点只有个分叉,每层结点至少有:第一层1、第二层2、第三层…第。则第层共有叶子结点(失败结点):

由于个关键字的B树必有个结点,则,即

B树的插入操作
1.结点分裂

【复习笔记】B树、B+树_子树_27


在插入后,若导致原结点关键字数超过上限,则从中间位置()将其中的关键字分为两部分,左部分包含关键字放在原结点中,右部分关键字放在新结点中,中间结点成为两者的父结点,分裂结果如下:

【复习笔记】B树、B+树_结点_30

插入88、90、99

【复习笔记】B树、B+树_子树_31


接着分裂

【复习笔记】B树、B+树_结点_32


插入70、83、87

【复习笔记】B树、B+树_结点_33


接着分裂

【复习笔记】B树、B+树_结点_34


插入92、93、94,分裂完成后

【复习笔记】B树、B+树_子树_35


插入73、74、75

【复习笔记】B树、B+树_结点_36


分裂终端结点

【复习笔记】B树、B+树_子树_37


此时可以看到根结点关键字太多了,需要分裂,按照同样的道理,分裂结果如下:

【复习笔记】B树、B+树_结点_38

对于分裂操作的总结:
  1. 对m阶B树,除根结点外,结点关键字个数
  2. 对于所有的结点,左子树 < 根结点 < 右子树
  3. 新的元素一定插入到终端结点
  4. 【复习笔记】B树、B+树_子树_40

B树的删除操作

现有如下的B树:

【复习笔记】B树、B+树_分块_41


删除60后:

【复习笔记】B树、B+树_分块_42


注:若待删除的关键字处于终端结点,则直接删除该关键字。同时还需检查结点关键字个数是否满足

删除80,此时根结点为空,可以利用当前关键字的直接前驱直接后继填补根结点

直接前驱: 当前关键字左子树“最右下”的元素,即左子树最大的关键字
直接后继: 当前关键字右子树“最左下”的元素,即右子树最小的关键字

下面利用直接后继填补根结点

【复习笔记】B树、B+树_子树_44


注:删除非终端结点80的操作,转换成了删除终端结点80的操作。故对非终端结点关键字的删除,必然可以抓换为对终端结点关键字的删除接着,我们删除关键字38,删除结果如下:

【复习笔记】B树、B+树_分块_45


可以看到,就是直接把38干掉,70提上去,49填下来,分别补到对应的位置

注:当右兄弟有充足的关键字时,用当前结点的后继(49),和后继的后继(70) 来填补

接下来,删除90,删除结果如下:

【复习笔记】B树、B+树_子树_46


同理,就是直接把90干掉,88填下来,87提上去,分别补到对应的位置

注:当左兄弟有充足的关键字时,用当前结点的前驱(88),和前驱的前驱(87) 来填补

接着删除49,先干掉49,结果如下:

【复习笔记】B树、B+树_结点_47


此时,对于最坐下的结点,只有25这一个关键字,而右兄弟又没有多余的关键字借给他。现在,我们将关键字25、70、71、72合并,结果如下:

【复习笔记】B树、B+树_结点_48


然而,此时关键字73所在的结点不符合B树的特性,我们将73、82、87、93合并,结果如下:

【复习笔记】B树、B+树_结点_49


注:兄弟不够借时,则将关键字删除后的结点与左(或右)兄弟结点,以及父结点中的关键字合并

【复习笔记】B树、B+树_分块_50


总结:

【复习笔记】B树、B+树_结点_51

五、B+树

【复习笔记】B树、B+树_结点_52


B树与B+树对比

【复习笔记】B树、B+树_结点_53


【复习笔记】B树、B+树_分块_54


【复习笔记】B树、B+树_分块_55


而B树中,所有结点的关键字 互不重复

【复习笔记】B树、B+树_结点_56


总结:

【复习笔记】B树、B+树_子树_57


精彩评论(0)

0 0 举报