1
0
mirror of https://github.com/Didnelpsun/CS408.git synced 2026-02-12 07:05:53 +08:00

更新查询

This commit is contained in:
Didnelpsun
2021-05-03 23:28:51 +08:00
parent b28bbb7ecf
commit 167ee582c9
2 changed files with 151 additions and 3 deletions

13
Code/search.h Normal file
View File

@@ -0,0 +1,13 @@
#include <stdio.h>
#include <stdlib.h>
#include "head.h"
// 分块查找
// 分块索引表
typedef struct {
// 块中最大元素
element_type max;
// 存储区间的最低索引和最高索引
int low, high;
} BlockSearchIndex;

View File

@@ -10,7 +10,7 @@
+ 查找长度:查找运算中,需要对比关键字的次数。
+ 平均查找长度ASL所有查找过程中进行关键字比较次数的平均值。$ASL=\sum_{i=1}^nP_iC_i$。其中$P_i$表示查找第i个元素的概率$C_i$表示查找第i个元素的查找长度。
## 静态查找
## 线性表查找
### 顺序查找
@@ -34,10 +34,14 @@ ASL查找失败为$\dfrac{1+2+3+\cdots+n+n}{n+1}=\dfrac{n}{2}+\dfrac{n}{n+1}$。
### 折半查找
也称为二分查找,只适用于有序的顺序表,链表无法适用。
也称为二分查找,只适用于有序的顺序表,链表无法适用,因为链表很难折半找到元素
#### 折半查找的结构
定义三个指针low指向查找范围的最小值high指向查找范围的最大值mid指向查找范围的中间值$mid=\lfloor(low+high)/2\rfloor$。(也可以向上取整,过程会有所不同)
#### 折半查找的过程
查找时首先计算出mid判断是否相等若查找值小于mid的值则将high赋值为mid的值若查找值大于mid则将low赋值为mid的值重新计算mid。这样就可以不断二分区间来查找从而加快迭代。当查找最后low>high则查找失败。
ASL查找成功为$\dfrac{1+2+3+\cdots+n}{n}=\dfrac{n+1}{2}$ASL查找失败为$n+1$,时间复杂度为$O(n)$。
@@ -54,4 +58,135 @@ ASL查找成功为$\dfrac{1+2+3+\cdots+n}{n}=\dfrac{n+1}{2}$ASL查找失败
ASL查找成功查找失败都一定小于折半查找树的树高时间复杂度为$O(\log_2n)$。
## 动态查找
### 分块查找
分块查找又称为索引顺序查找,需要对数据进行一定的排序,不一定全部是顺序的,但是要求在一个区间内是满足一定条件的,即块内无序,块间有序。其中分割的块数和每块里的数据个数都是不定的。
#### 分块查找的结构
除了保存数据的顺序表外还需要定义一个索引表,保存每个分块的**最大关键字**和每块的存储空间。
很明显这种定义方式定义了两个顺序结构,并且如果插入删除时需要大量移动元素,所以可以采用链表的形式。
定义一种最大元素结点,包含数据、指向后继最大元素结点的指针、指向分块内元素的指针;定义一种块内元素结点,包含数据、指向后继分块内元素的指针。但是这时候就无法折半查找,只能顺序查找。
所以总的来说分块查找还是一种静态查找,动态插入删除的效率较低。
#### 分块查找的过程
在查找时先根据关键字遍历索引表,然后找到索引表的分块(可以顺序也可以折半),再到存储数据的顺序表的索引区间中查找。
若适用折半查找查找索引表的分块索引表中若不存在目标关键字则折半查找索引表最终会停在low>high要在low所指向分块中查找。
#### 分块查找的效率
ASL查找成功失败的情况都十分复杂所以一般不会考。
假设长度为$n$的查找表被均匀分为$b$块,每块$s$个元素假设索引查找和块内查找的平均查找长度ASL分别为$L_I$和$L_S$,则分块查找的平均查找长度为$ASL=L_I+L_S$。
使用顺序查找索引表,则$L_I=\dfrac{(1+2+\cdots+b)}{b}=\dfrac{b+1}{2}$$L_S=\dfrac{(1+2+\cdots+s)}{s}=\dfrac{s+1}{2}$。所以$ASL=\dfrac{b+1}{2}+\dfrac{s+1}{2}=\dfrac{s^2+2s+n}{2s}$,当$s=\sqrt{n}$时,$ASL_{min}=\sqrt{n}+1$。
使用折半查找索引表,则$L_I=\lceil\log_2(b+1)\rceil$$L_S=\dfrac{(1+2+\cdots+s)}{s}=\dfrac{s+1}{2}$。所以$ASL=\lceil\log_2(b+1)\rceil+\dfrac{s+1}{2}$。
## 数表查找
### B树
#### B树的定义
为了保证m叉查找树中每个结点都能被有效利用避免大量结点浪费导致树高过大所以规定m叉查找树中除了根结点以外任何结点至少有$\lceil m/2\rceil$个分叉,即至少包含$\lceil m/2\rceil-1$个结点。
为了保证m叉查找树是一棵平衡树避免树偏重导致树高过大所以规定m叉查找树中任何一个结点其所有子树的高度都要相同。
而能保证这两点的查找树就是一棵B树多少叉就是一棵多少阶的B树。
B树即多路平衡查找树所有结点的孩子个数的最大值就是B树的阶一般用m表示。
#### B树的性质
+ 树的每个结点至多包含m棵子树至多包含m-1个关键字。
+ B树最底端的失败的不存在的结点就是常说的叶子结点而最底端的存在数据的结点就是终端结点。一般的树的叶子结点和终端结点都是指最底端的有数据的结点
+ 若根结点不是终端结点,则至少有两颗子树,任意结点的每棵子树都是绝对平衡的。
+ 除根结点以外的所有非叶结点至少有$\lceil m/2\rceil$棵子树,即至少包含$\lceil m/2\rceil-1$个结点。
+ 所有叶结点都出现在同一个层次上且不带信息。
+ 每个结点中的关键字是有序的。子树0 < 关键字1 < 子树2 < ...。
计算B树高度大部分不包括叶子结点。若含有n个关键字的m阶B树。
+ 最小高度:让每个结点尽可能满,有$m-1$个关键字,$m$个分叉,则一共有$(m-1)(m^0+m^1+m^2+\cdots+m^{h-1})$个结点,其中$n$小于等于这个值,从而求出$h\geqslant\log_m(n+1)$。
+ 最大高度:
+ 让各层分叉尽可能少,即根结点只有两个分叉,其他结点只有$\lceil m/2\rceil$个分叉所以第一层1个第二层2个第$h$层$2(\lceil m/2\rceil)^{h-2}$个结点,而$h+1$层的叶子结点有$2(\lceil m/2\rceil)^{h-1}$个,且$n$个关键字的B树必然有$n+1$个叶子结点,从而$n+1\geqslant2(\lceil m/2\rceil)^{h-1}$,即$h\leqslant\log_{\lceil m/2\rceil}\dfrac{n+1}{2}+1$。
+ 让各层关键字尽可能少,记$k=\lceil m/2\rceil$。第一层最少结点数和最少关键字为1第二层最少结点数为2最少关键字为$2(k-1)$,第三层最少结点数为$2k$,最少关键字为$2k(k-1)$,第$h$层最少结点数为$2k^{h-2}$,最少关键字为$2k^{h-2}(k-1)$,从而$h$层的m阶B数至少包含关键字总数$1+2(k-1)(k^0+k^1+\cdots+k^{h-2})=1+2(k^{h-1}-1)$,若关键字总数小于这个值,则高度一定小于$h$,所以$n\geqslant 1+2(k^{h-1}-1)$,则$h\leqslant\log_{\lceil m/2\rceil}\dfrac{n+1}{2}+1$。
#### B树的插入
新元素插入一定是插入到最底层的终端结点使用B树的查找来确定插入位置。
若导致原结点关键字数量超过上限溢出,就从中间位置$\lceil m/2\rceil$分开,将左部分包含的关键字放在原来结点,右部分包含的关键字放在一个新结点,并插入到原结点的父结点的后一个位置上,而在原结点的父结点连接后的结点后移一个连接让位给分割出来的右半部分结点,中间的一个结点$\lceil m/2\rceil$插入到原结点的父结点上,并考虑在父结点的顺序。
若父结点插入时也溢出了,则同理在父结点的中间进行分割,左半部分在原来父结点;右半部分新建一个父结点,并把中间结点右边开始的所有连接移动到新父结点上;中间的结点上移到祖父结点,如果没有就新建,然后建立两个指针分别指向原父结点和新父结点。
#### B树的删除
+ 若被删除关键字在终端结点,且结点关键字个数不低于下限,则直接删除该关键字,并移动后面的关键字。
+ 若被删除关键字在非终端结点,则用直接前驱或直接后继来替代被删除关键字,然后后面的元素直接前移。
+ 直接前驱:当前关键字左侧指针所指子树最右下的元素。
+ 直接后继:当前关键字右侧指针所指子树最左下的元素。
+ 若被删除关键字在终端结点,但是结点关键字个数删除后低于下限:
+ 右兄弟够借:若原结点右兄弟结点里的关键字在删除一个后高于下限,则可以用结点的后继以及后继的后继来顶替:
1. 将原结点在父结点的连接的前一个关键字下移到原结点并放在最后面。
2. 原结点父结点里的关键字全部前移一位。
3. 将原结点右兄弟结点的第一个关键字上移插入到原结点父结点的关键字的最后面。
4. 原结点右兄弟结点里的关键字全部前移一位。
+ 左兄弟够借:若原结点里右兄弟的关键字在删除一个后低于下限,但是左兄弟的结点足够,则可以用结点的前驱以及前驱的前驱来顶替:
1. 将原结点在父结点的连接的前一个关键字下移到原结点并放在最前面。
2. 原结点父结点里的关键字全部前移一位。
3. 将原结点左兄弟结点的最后一个关键字上移插入到原结点父结点的关键字的最前面。
4. 原结点兄左弟结点里的关键字全部前移一位。
+ 左右兄弟都不够借:若 左右兄弟结点的关键字个数均等于下限值,则将关键字删除后与左或右兄弟结点以及父结点中的关键字进行合并:
1. 将原结点的父结点关键字插入到原结点关键字后面。
2. 将原结点的左或右兄弟结点的关键字合并到原结点(前插或后插),并将连接也转移到原结点上。
3. 若父结点的关键字个数又不满于下限,则父结点同样要于与它的兄弟父结点进行合并,并不断重复这个过程。
### B+树
B+树考的并不是很深。
与分块查找类似是对B树的一种变型。
#### B+树的定义
一个m阶的B+树需要满足以下条件:
1. 每个分支结点最多有m棵子树或孩子结点。
2. 为了保持绝对平衡,非叶根结点至少有两棵子树,其他每个分支结点至少有$\lceil m/2\rceil$棵子树。不同于B树B+树又重新将最下面的保存的数据定义为叶子结点)
3. 结点的子树个数与关键字个数相等。B树结点子为树个数与关键字个数加1
4. 所有叶结点包含所有关键字以及指向记录的指针叶结点中将关键字按大小排序并且相邻叶子结点按大小顺序相互连接起来。所以B+树支持顺序查找。
5. 所有分支结点中仅包含其各子结点中关键字的最大值以及指向其子结点的指针(即分支结点只是索引)。
#### B+树的查找
无论查找成功与否B+树的查找一定会走到最下面一层结点否则无法确认。而B树查找可以停留在任何一层。
B+树可以遍历查找,即从根结点出发,对比每个结点的关键字值,若目标值小于当前关键字值且大于前一个关键字值,则从当前关键字的指针向下查找。
B+树可以顺序查找,在叶子结点的块之间定义指向后面叶子结点块的指针,从而能顺序查找。
#### B+树与B树的区别
对于m阶B+树与B树
&nbsp;|B+树|B树
:--:|:--:|:--:
结点的n个关键字对应的子树个数|$n$|$n+1$
根结点的关键字数|$[1,m]$|$[1,m-1]$
其他结点的关键字数|$[\lceil m/2\rceil,m]$|$[\lceil m/2\rceil-1,m-1]$
关键字分布|叶子结点包含所有关键字,非叶结点包含部分重复关键字|所有结点的关键字不重复
结点作用|叶子结点包含信息,非叶子结点是索引作用|所有结点都包含信息
结点存储内容|叶子结点包含关键字与对应记录的存储地址,非叶子结点包含对应子树的最大关键字和指向该子树的指针|所有结点都包含关键字与对应记录存储地址
查找位置|需要查找到叶子结点的最底层才能判断是否查找成功或失败|查找到数的任何地方都能判断
查找速度|非叶子结点不包含关键字对应记录的存储地址,可以使一个磁盘块含有多格关键字,从而让树的阶数更大,树更矮,读磁盘次数更是,查找更快|所有结点都包含存储地址,保存的关键字数量更少,树高更高,所以读写磁盘次数更多,查找更慢
## 散列表查找
### 散列表定义