查找
1、 基本概念
- 适合静态查找表:顺序查找、折半查找、散列查找
- 适合动态查找表:二叉排序树的查找(二叉平衡树和B树是二叉排序树的改进)、散列查找
- 平均查找长度 A S L = ∑ i = 1 n P i C i ASL=\sum_{i=1}^n{P_i}{C_i} ASL=∑i=1nPiCi
2、 线性结构
2.1 顺序查找
从表的一端开始,逐个检查关键字是否满足给定条件
优点:对数据存储没有要求,顺序或者链式都可。注意对线性链表只能进行顺序查找。
typedef struct{
int *elem;
int TableLen;
}SSTable;
int Search_Seq(SSTable ST, int key){ // 在顺序表ST中寻找关键字为key的元素,找到返回该元素在表中位置
ST.elem[0]=key; // 设置0位置,当表中无待查找元素时,函数返回0
for(int i=ST.TableLen; ST.elem[i]!=key; i--);
return i;
}
一般线性表
A
S
L
成
功
=
∑
i
=
1
n
P
i
C
i
=
∑
i
=
1
n
1
n
∗
(
n
−
i
+
1
)
=
n
+
1
2
ASL_{成功}=\sum_{i=1}^n{P_i}{C_i}=\sum_{i=1}^n{\frac 1n}*(n-i+1)=\frac{n+1}2
ASL成功=∑i=1nPiCi=∑i=1nn1∗(n−i+1)=2n+1
A
S
L
失
败
=
∑
i
=
1
n
P
i
C
i
=
∑
i
=
1
n
1
n
∗
(
n
+
1
)
=
n
+
1
ASL_{失败}=\sum_{i=1}^n{P_i}{C_i}=\sum_{i=1}^n{\frac 1n}*(n+1)=n+1
ASL失败=∑i=1nPiCi=∑i=1nn1∗(n+1)=n+1
有序表
A
S
L
失
败
=
∑
i
=
1
n
P
i
C
i
=
∑
i
=
1
n
1
n
+
1
∗
(
i
+
1
)
=
n
2
+
n
n
+
1
ASL_{失败}=\sum_{i=1}^n{P_i}{C_i}=\sum_{i=1}^n{\frac 1{n+1}}*(i+1)=\frac n2+\frac n{n+1}
ASL失败=∑i=1nPiCi=∑i=1nn+11∗(i+1)=2n+n+1n
2.2 折半查找
又称为二分查找,每次查找失败时,只能从中间元素以外的前半部分或后半部分继续。
所用结构必须具有随机存取的特性,故仅适用于有序的顺序表。
A S L 成 功 = 1 n ∑ i = 1 n l i = 1 n ( 1 × 1 + 2 × 2 + . . . + h × 2 h − 1 ) = n + 1 n l o g 2 ( n + 1 ) − 1 ASL_{成功}=\frac 1n \sum_{i=1}^n{l_i}=\frac 1n (1×1+2×2+...+h×2^{h-1})=\frac {n+1}{n} log_2{(n+1)}-1 ASL成功=n1i=1∑nli=n1(1×1+2×2+...+h×2h−1)=nn+1log2(n+1)−1
2.3 分块查找
又称索引顺序查找,将查找表分为若干子块,块内元素可以无序,块之间有序。建立索引表,索引表记录各块中的最大关键字,以及对应地址,按照关键字有序排列
- 确定待查找值在哪个块(折半查找)
- 在确定块中查找待查找值(顺序查找)
若将长度n的查找表分为b块,每块有s个记录,等概率情况下
- 块内和索引表均采用顺序查找
A S L 分 块 = A S L 块 间 顺 序 + A S L 块 内 顺 序 = b + 1 2 + s + 1 2 ASL_{分块}=ASL_{块间顺序}+ASL_{块内顺序}=\frac {b+1}2+\frac {s+1}2 ASL分块=ASL块间顺序+ASL块内顺序=2b+1+2s+1 - 块间采用折半查找 ,块内采用顺序查找
A S L 分 块 = A S L 折 半 + A S L 顺 序 = ⌈ log 2 ( b + 1 ) ⌉ + s + 1 2 ASL_{分块}=ASL_{折半}+ASL_{顺序}=⌈\log_2(b+1)⌉+\frac {s+1}2 ASL分块=ASL折半+ASL顺序=⌈log2(b+1)⌉+2s+1
3、 树形结构
3.1 二叉排序树
见树与二叉树部分
3.2 二叉平衡树
3.3 B树、B+树
2-3树
2-3树是一种多路查找树:2和3的意思就是2-3树包含两种结点
- 2结点包含一个元素和两个孩子(或者没有孩子)。
①左子树包含的元素小于该结点的元素值,右子树包含的元素大于该结点的元素值
②2结点要不有两个孩子,要不就没有孩子,不允许有一个孩子 - 3结点包含一大一小两个元素和三个孩子(或者没有孩子)。(两个元素按大小顺序排列好)
①左子树包含的元素小于该结点较小的元素值,右子树包含的元素大于该结点较大的元素值,中间子树包含的元素介于这两个元素值之间。
②3结点要不有三个孩子,要不就没有孩子,不允许有一个或两个孩子 - 2-3树所有叶子结点都在同一层次
2-3-4树
2-3-4树也是一种多路查找树:2和3和4的意思就是2-3-4树包含三种结点
- 2结点包含一个元素和两个孩子(或者没有孩子)。
①左子树包含的元素小于该结点的元素值,右子树包含的元素大于该结点的元素值
②2结点要不有两个孩子,要不就没有孩子,不允许有一个孩子 - 3结点包含一大一小两个元素和三个孩子(或者没有孩子)。
①左子树包含的元素小于该结点较小的元素值,右子树包含的元素大于该结点较大的元素值,中间子树包含的元素介于这两个元素值之间。
②3结点要不有三个孩子,要不就没有孩子,不允许有一个或两个孩子 - 4结点包含小中大三个元素和四个孩子(或者没有孩子)。
①左子树包含的元素小于该结点最小的元素值,第二个子树包含大于最小的元素值小于中间元素值的元素,第三个子树包含大于中间元素值小于最大元素值的元素,右子树包含的元素大于该结点最大的元素值。
②4结点要不有四个孩子,要不就没有孩子,不允许有一个或两个或三个孩子 - 2-3-4树所有叶子结点都在同一层次
B树(B-树)
B树是一种所有结点的平衡因子均为0的多路查找树,2-3树和2-3-4树都是B树的特例,把树中结点最大的孩子数目称为B树的阶。通常记为m。 一棵m阶B树或为空树,或为满足如下特性的m叉树:
- 有n个分支的结点,有n-1个关键字,k=2(根结点)或⌈m/2⌉(非根结点)
- 树中每个结点至多有m棵子树。(即至多含有m-1个关键字) (“两棵子树指针夹着一个关键字”)
- 若根结点不是终端结点,则至少有两棵子树。(至少一个关键字)
- 除根结点外的所有非叶结点至少有⌈m/2⌉棵子树。(即至少含有⌈m/2⌉-1个关键字)
- 关键字个数n为2(⌈m/2⌉)(h-1)-1≤n≤mh-1
B树相关操作
- 查找
查找过程:①待查找关键字key和结点的中的关键字比较,如果等于其中某个关键字,则查找成功。
②如果和所有关键字都不相等,则看key处在哪个范围内,然后去对应的指针所指向的子树中查找。 - B树的插入操作
当插入后关键字个数大于m-1时,对结点进行分裂,如下图。
分裂的方法:取这个数组中的中间关键字(⌈n/2⌉)插入到原结点的父结点,然后其他关键字形成两个结点作为新结点的左右孩子。 - B树的删除操作
1)如果删除的关键字在终端结点上(最底层非叶子结点):
①结点内关键字数量大于⌈m/2⌉-1 ,直接删除。
②结点内关键字数量等于⌈m/2⌉-1 ,并且其左右兄弟结点中存在关键字数量大于⌈m/2⌉-1 的结点,则去兄弟阶段中借关键字。
③结点内关键字数量等于⌈m/2⌉-1 ,并且其左右兄弟结点中不存在关键字数量大于⌈m/2⌉-1 的结点,则需要进行结点合并。
2)如果删除的关键字不在终端结点上:需要先转换成在终端结点上,再按照在终端结点 上的情况来分别考虑对应的方法。
相邻关键字:对于不在终端结点上的关键字,它的相邻关键字是其左子树中值最大的关键字或者右子树中值最小的关键字。
第一种情况:存在关键字数量大于⌈m/2⌉-1 的左子树或者右子树,在对应子树上找到该关键字的相邻关键字,然后将相邻关键字替换待删除的关键字。
第二种情况:左右子树的关键字数量均等于⌈m/2⌉-1 ,则将这两个左右子树结点合并,然后删除待删除关键字。
B+树
B+树是常用于数据库和操作系统的文件系统中的一种用于查找的数据结构
m阶的B+树与m阶的B树的主要差异在于:
- 在B+树中,具有n个关键字的结点只含有n个分支(每个关键字对应一个分支);而在B树中,具有n个关键字的结点含有(n+1)个分支。
- 在B+树中,每个结点(非根内部结点)关键字个数n的范围是 ⌈m/2⌉≤n≤m(根结点1≤n≤m),在B树中,每个结点(非根内部结点)关键字个数n的范围是⌈m/2⌉-1≤n≤m-1(根结点:1≤n≤m-1)。
- 在B+树中,叶结点包含了全部关键字,即在非叶结点中出现的关键字也会出现在叶结点中;而在B树中,叶结点包含的关键字和其他结点包含的关键字是不重复的。
- 所有非叶结点仅起到索引作用,非叶结点中的每个索引项只含有对应子树的最大关键字和指向该子树的指针,不含有该关键字对应记录的存储地址。 B树中,每个关键字对应一个记录的存储地址
- 在B+树中,有一个指针指向关键字最小的叶子结点,所有结点链接成一个线性链表
- B+树支持顺序查找,B树不支持(B树只支持多路查找)
4、 散列结构
4.1 定义
根据给定的关键字来计算出关键字在表中的地址。也就是说,散列表建立了关键字和存储地址之间的一种直接映射关系。
4.2 构造散列函数
散列函数:一个把查找表中的关键字映射成该关键字对应的地址的函数,记为Hash(key)=Addr。
构造方法:
- 直接定址法:直接取关键字的某个线性函数值为散列地址,散列函数为H(key)=a×key+b。式中,a和b是常数。这种方法计算最简单,并且不会产生冲突
- 除留余数法:假定散列表表长为m,取一个不大于m但最接近或等于m的质数p,利用以下公式把关键字转换成散列地址。散列函数为H(key)=key % p 除留余数法的关键是选好p,使得每一个关键字通过该函数转换后等概率地映射到散列空间上的任一地址,从而尽可能减少冲突的可能性
- 数字分析法:设关键字是r进制数(如十进制数),而r个数码在各位上出现的频率不一定相同,可能在某些位上分布均匀些,每种数码出现的机会均等;而在某些位上分布不均匀,只有某几种数码经常出现,则应选取数码分布较为均匀的若干位作为散列地址。这种方法适合于已知的关键字集合
- 平方取中法:顾名思义,取关键字的平方值的中间几位作为散列地址。具体取多少位要看实际情况而定。这种方法得到的散列地址与关键字的每一位都有关系,使得散列地址分布比较均匀。
- 折叠法:将关键字分割成位数相同的几部分(最后一部分的位数可以短一些),然后取这几部分的叠加和作为散列地址,这种方法称为折叠法。关键字位数很多,而且关键字中每一位上数字分布大致均匀时,可以采用折叠法得到散列地址。
4.3 Hash冲突
散列函数可能会把两个或两个以上的不同关键字映射到同一地址,称这种情况为“冲突”,这些发生碰撞的不同关键字称为同义词。
处理办法:
- 开放定址法:将产生冲突的Hash地址作为自变量,通过某种冲突解决函数得到一个新的空闲的Hash地址。
1)线性探测法:冲突发生时,顺序查看表中下一个单元(当探测到表尾地址m-1时,下一个探测地址是表首地址0),直到找出一个空闲单元(当表未填满时一定能找到一个空闲单元)或查遍全表。
2)平方探测法:设发生冲突的地址为d,平方探测法得到的新的地址序列为d+12,d-12,d+22,d-22… 平方探测法是一种较好的处理冲突的方法,可以避免出现“堆积”问题,它的缺点是不能探测到散列表上的所有单元,但至少能探测到一半单元。
3)再散列法:又称为双散列法。需要使用两个散列函数,当通过第一个散列函数H(Key)得到的地址发生冲突时,则利用第二个散列函数Hash2(Key)计算该关键字的地址增量。
4)伪随机序列法:当发生地址冲突时,地址增量为伪随机数序列,称为伪随机序列法。 - 拉链法:对于不同的关键字可能会通过散列函数映射到同一地址,为了避免非同义词发生冲突,可以把所有的同义词存储在一个线性链表中,这个线性链表由其散列地址唯一标识。拉链法适用于经常进行插入和删除的情况。
4.4 散列表的查找过程
类似于构造散列表,给定一个关键字Key。 先根据散列函数计算出其散列地址。然后检查散列地址位置有没有关键字。 1)如果没有,表明该关键字不存在,返回查找失败。 2)如果有,则检查该记录是否等于关键字。 ①如果等于关键字,返回查找成功。 ②如果不等于,则按照给定的冲突处理办法来计算下一个散列地址,再用该地址去执行上述过程。
4.5 散列表的查找性能
装填因子
α
=
表
中
记
录
数
n
散
列
表
长
度
m
α=\frac{表中记录数n}{散列表长度m}
α=散列表长度m表中记录数n
ASL与α有关,α越大,表示装填的记录越“满”,发生冲突的可能性就越大,反之发生冲突的可能性越小
考研tips:
- 散列查找:散列表的构造、冲突处理方法、ASL成功和ASL失败、散列表的特征的性能分析
- 折半查找:过程、构造判定树、ASL成功和ASL失败
- B树:插入、删除、查找过程
- B+树:基本概念和性质
小狼的相关博文: