目录
7 查找
7.1 查找的基本概念
7.2 顺序查找和折半查找
7.2.1 顺序查找
7.2.2 折半查找
7.2.3 分块查找
7.3 B树和B+树
7.3.1 B树
7.3.2 B树的插入删除
7.3.3 B+树
7.4 散列表
7.4.1 散列查找
查找——在数据集合中寻找满足某种条件的数据元素的过程称为查找
查找表(查找数据)——用于查找的数据集合称为查找表,它由同一类型的数据元素(或记录)组成
关键字——数据元素中唯一标识该元素的某个数据项的值,使用基于关键字的查找,查找结果应该是唯一的。
对查找表的常见操作:
1.查找符合条件的数据元素
2.插入,删除某个数据元素
查找算法的评价指标:
查找长度——在查找运算中,需要对比关键字的次数称为查找长度
平均查找长度——所有查找过程中进行关键字的比较次数的平均值
顺序查找,又叫“线性查找”,通常用于线性表。
算法思想:从头到尾挨个找
代码实现:
typedef struct {//查找表的数据结构(顺序表)
ElemType* elem;//动态数组基址
int TableLen;//表的长度
}SSTable;
//顺序查找
int Search_Seq(SSTable ST, ElemType key) {
int i;
for (i = 0; i < ST.TableLen && ST.elem[i] != key; i++) {
//查找成功,则返回元素下标;查找失败,则返回-1
return i == ST.TableLen ? -1 : i;
}
}
折半查找又称“二分查找”,仅适用于有序的顺序表
//折半查找
int Binary_Search(SSTable L, ElemType key) {
int low = 0, high = L.TableLen - 1, mid;
while(low + high) {
mid = (low + high) / 2;
if (L.elem[mid] == key)
return mid;
else if (L.elem[mid] > key)
high = mid - 1;
else
low = mid + 1;
}
return -1;
}
折半查找判定树的构造:
如果当前low和high之间有奇数个元素,则mid分隔后,左右两部分元素个数相等
如果当前low和high之间有偶数个元素,则mid分隔后,左半部分比右半部分少一个元素
折半查找判定树中,若,则对于任何一个结点,必有:右子树结点数-左子树结点树=0或1
折半查找的判定树一定是平衡二叉树,折半查找的判定树中,只有最下面一层是不满的,因此,元素个数为n时树高
判定树结点关键字:左<中<右,满足二叉排序树的定义
失败结点:n+1个(等于成功结点的空链域数量)
折半查找的时间复杂度=
算法思想:
1.在索引表中确定待查记录所属的分块(可顺序,可拆半)
2.在块内顺序查找
5叉查找树
最多1个关键字,2个分叉
最多4个关键字,5个分叉
struct Node {
ElemType keys[4];//最多4个关键字
struct Node* child[5];//最多五个孩子
int num;//结点中有几个关键字
};
若每个结点内关键字太少,导致树变高,要查更多层结点,效率低
策略1:m叉查找树中,规定除了根节点外,任何结点至少[m/2]个分叉,即至少含有[m/2]-1个关键字
eg:对于5叉排序树,规定除了根节点外,任何结点都至少有3个分叉,2个关键字
如何保证查找效率:
策略2:m叉查找树中,规定对于任何一个结点,其所有子树的高度都要相同。
如果能同时满足策略1和策略2,那么这就是一棵B树
B树,又称为多路平衡查找树,B树中所有结点的孩子个数的最大值称为B树的阶,通常用m表示。一棵m阶B树或为空树,或为满足如下特性的m叉树:
1.树中每个结点至多有m棵子树,即至多含有m-1个关键字
2.若根结点不是终端结点,则至少有两棵子树
3.除根结点外的所有非 叶子结点至少有[m/2]棵子树,即至少含有[m/2]-1个关键字
4.所有的叶结点都出现在同一层次上,并且不带信息(可以视为外部结点或类似于半折查找判定树的查找失败结点,实际上这些结点不存在,指向这些结点的指针为空)
5.所有非叶结点的结构如下:
对于含有n个关键字的m叉B树,其树高h满足:
B树的插入:
5阶B树——结点关键字个数
即:
新元素一定是插入到最底层“终端节点”,用“查找”来确定插入位置
在插入key后,若导致原结点关键字数量超过上限,则从中间位置[m/2]将其中的关键字分为两部分,左部分包含的关键字放在原结点中,右部分包含的关键字放到新结点中,中间位置[m/2]的结点插入原结点的父结点。若此时导致其父结点的关键字个数也超过了上限,则继续进行这种分裂操作,直至这个过程传到根结点为止,进而导致B树高度增1。
B树的删除:
若被删除关键字在非终端节点,则用直接前驱或直接后驱来替代被删除的关键字
直接前驱:当前关键字左侧指针所指子树中“最右下”的元素
直接后继:当前关键字右侧指针所指子树中“最左下”的元素
对非终端结点关键字的删除,必然可以转化为对终端结点的删除操作
当右兄弟很宽裕时,用当前结点的后继,后继的后继来填补空缺
兄弟不够借。若被删除关键字所在结点删除前的关键字低于下限,且此时与该结点相邻的左,右兄弟结点的关键字个数均,则将关键字删除后与左(或右)兄弟结点及双亲结点中的关键字进行合并
在合并过程中,双亲结点中的关键字个数会减一,若其双亲结点时根结点且关键字个数减少至0,则直接将根结点删除,合并后的新结点成为根;若双亲结点不是根结点,且关键字个数减少到[m/2]-2则又要与它自己的兄弟结点进行调整或合并操作,并重复上述步骤,直至符合B树的要求为止。
一棵m阶的B+树需满足下列条件:
1.每个分支结点最多有m棵树(孩子结点)。
2.非叶根结点至少有两棵子树,其他每个分支至少有[m/2]棵子树。
3.结点的子树个数与关键字个数相等。
4.所有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键字按大小顺序排列,并且相邻叶结点按大小顺序相互链接起来。
5.所有分支结点中仅包含它的各个结点中关键字的最大值指向其子结点的指针
在B+树中,非叶结点不含有该关键字对应记录的存储地址。可以使一个磁盘块可以包含更多个关键字,使得B+树的阶更大,树高更矮,读磁盘次数更少,查找更快
散列表:又称哈希表,是一种数据结构,特点是:数据元素的关键字与其存储地址直接相关。
通过散列函数(哈希函数)Addr=H(key)来建立关键字与存储地址的联系。
若不同的关键字通过散列函数映射 到同一个值,则称它们为“同义词”。通过散列函数确定的位置已经存放了其他元素,则称这种情况为“冲突”。
用拉链法(又称链接法,链地址法)处理“冲突”:把所有“同义词”存储在一个链表中
散列查找:
通过散列函数计算目标元素存储地址
常见的散列函数:
设计目标——让不同关键字的冲突尽可能地少
除留余数法——H(key)=key%p
散列表表长为m,取一个不大于m但最接近或等于m的质数p
用质数取模,分布更均匀,冲突更少。
tips:散列函数的设计要结合实际的关键字分布特点来考虑,不要教条化
直接定址法——H(key)=key或H(key)=a*key+b
其中,a和b时常数。这种方法计算最简单,且不会产生冲突。它适合关键字的分布基本连续的情况,若关键字分布不连续,空位较多,则会造成存储空间的浪费。
数字分析法——选取数码分布较为均匀的若干位作为散列地址
设关键字是r进制数,而r个数码在各位上出现的频率不一定相同,可能在某些位上分布均匀一些,每种数码出现的机会均等;而在某些位上分布不均匀,只有某几种数码经常出现,此时可选取数码分布较为均匀的若干位作为散列地址。这种方法适合于已知的关键字集合,若更换了关键字,则需要重新构造新的散列函数。
平方取中法——取关键字的平方值的中间几位作为散列地址
具体取多少位要视实际情况而定。这种方法得到的散列地址与关键字的每位都有关系,因此使得散列地址分布比较均匀,适用于关键字的每位取值都不够均匀或均小于散列地址所需的位数。
散列查找是典型的“用空间换时间”的算法,只要散列函数设计的合理,则散列表越长,冲突的概率越低
开放定址法:
是指可存放新表项的空闲地址既向它的同义词表项开放,又向它的非同义词表项开放。其数学递推公式为:
m表示散列表长;为增量序列;i可理解为“第i次发生冲突”
1.线性探测法——=0,1,2,3,...,m-1;即发生冲突时,每次往后探测相邻的下一个单元是否为空
2.平方探测法——当时,称为平方探测法,又称二次探测法其中
比起线性探测法更不容易产生“聚集”问题
散列表长度必须是一个可以表示成4j+3的素数,才能探测到所有位置
3.伪随机序列法——是一个伪随机序列,如
再散列法:
除了原始的散列函数H(key)之外,多准备几个散列函数,当散列函数冲突时,用下一个散列函数计算一个新地址,直到不冲突为止