1.查找的基本概念
关于查找的几个术语
- 查找——在数据集合中寻找满足某种条件的数据元素的过程称为查找
- 查找表(查找结构)——用于查找的数据集合称为查找集,他由同一类的数据元素(或记录组成)
- 关键字——数据元素中唯一标识该元素的某个数据项的值,使用基于关键字的查找,查找结果应该是唯一的。
查找算法的评价指标
- 查找长度——在查找算法中,需要对比关键字的次数称为查找长度
- 平均查找长度(ASL)——所有查找过程中进行关键字比较的平均值。
2.顺序查找
顺序查找又称线性查找,常用于线性表,时间复杂度O(n)
typedef struct{ //查找表的数据结构(线性表)
ElemType *elem; //动态数组基址,从0开始
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;
}
查找效率分析ASL
假设有一个长度为n的查找表,要查找每个元素的概率都是1/n。
则ASL成功=(1+2+3+……n)/n=(n+1)/2
ASL失败=n+1
T(n)=O(n)
算法的优化
- 若表中元素有序——当关键字大于或小于目标关键字时,查找失败(可以降低失败时的ASL)
- 若每个关键字被查找的概率不同——可按查找概率降序排序(可以降低查找成功时的ASL)
3.折半查找
折半查找,又称二分查找,只适用于有序的顺序表。
typedef struct{
int *elem;
int TableLen;
}SSTable;
int Binary_Serch(SSTable L,int 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;
}
查找效率分析ASL
假设顺序表有11个元素,下面是他的折半查找判定树,紫色部分为失败结点。
时间复杂度:O(log2n)
折半查找判定树的构造
- 如果当前low和high之间有奇数个元素,则mid分割后,左右两部分元素个数相等。
- 若有偶数个元素,左半部分的元素会比右半部分少一个
右子树结点-左子树结点=0或1,一定是一颗平衡二叉树,且判定树结点关键字:左<中<右,满足二叉排序树的定义,失败结点有n+1个
注意事项
大部分情况下折半查找的效率比顺序查找要高,但并非绝对,比如说要查找的元素就在数组的第一个位置。
4.分块查找(手算)
分块查找的思想
块内无序,块间有序
typedef struct{
int maxValue;
int low,high;//记录了存储的区间范围
}Index;
分块查找的算法思想
- 在索引表中确定待查记录所属的分块(可顺序可折半)
- 在快内顺序查找
若索引表不包含目标关键字,则折半查找索引表最终停在low>high,要在low所指的分块中查找
查找效率分析ASL
5.B树(难点)
5.1B树的基本概念
如何保证n叉树的查找效率
- m叉查找树中,规定除根节点外,任何结点至少有[m/2]个分叉,即至少含有[m/2]-1个关键字。(否则会导师每个结点的关键字太少,树变高,要查找更多层结点,效率低)
- m叉查找树中,规定对于任何一个结点,其所有子树的高度都要相同。(保证平衡)
B树的概念
m阶B数的核心特征
B树的高度
注:此处的高度不包括叶子结点
最小高度——让每个结点尽可能得满,有m-1个关键字,m个分叉
则h=(m-1)(1+m+m2+…+mh-1)=mh-1
最大高度——让各层的分叉尽可能的少,即根节点只有2个分叉,其他结点只有[m/2]个分叉,各层结点至少有:1,2,2[m/2]…2([m/2])h-2,且每个结点的关键字也尽可能的小,为[m/2]-1
5.2B树的插入和删除
这里以5阶B树为例
结点关键字数:[m/2]-1~m-1,即2-4
插入
新元素一定是插入到最底层终端节点
在插入key后,若导致原结点关键字超过上限,则从中间位置([m/2])将其中的关键字分为两部分,左部分包含的关键字放在源节点中,右部分包含的关键字放到新节点中,中间位置([m/2])的结点插入源节点的父结点,若导致父结点的关键字个数也超过了上限,则继续进行这种分裂操作
删除
若被删除的关键字在终端节点,则直接删除该关键字(要注意结点关键字的个数是否低于下限[m/2]-1)
若被删除关键字在非终端结点,则用直接前驱或直接后继来代替被删除的关键字,直接前驱:当前关键字左侧指针所指子树中“最右下”的元素
对于非终端节点,也可以通过直接后继来进行删除
直接后继:当前关键字右侧指针所指子树中最左下的元素
兄弟够借。若被删除关键字所在节点删除后的关键字个数低于下限,且节点右或左兄弟结点的关键字个数还很宽裕,则需调整该结点右或左兄弟节点及其双亲结点。
- 当右兄弟很宽裕时,用当前节点的后继,后继的后继来填补空缺
- 当左兄弟很宽裕时,用当前节点的前驱,前驱的前驱来填补空缺
兄弟不够借。若被删除关键字所在结点删除后的关键字个数低于下限,且此时其左右兄弟结点关键字个书均不够借,则将关键字删除后的左或右兄弟节点及双亲结点中的关键字进行合并
5.3B+树
m阶B+树需满足的条件
- 1.每个分支结点最多有m
- 非叶和非根结点至少有两颗子树,其他每个分支至少有[m/2]棵子树
- 结点的子树个数与关键字个数相等
- 所有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键字大小顺序排列,并且相邻叶结点按大小顺序相互链接起来。
- 所有分支结点中仅包含他的各个子结点中关键字的最大值及指向其子结点的指针
B+树的查找
通过分支结点,确定要查找的元素所在哪个叶子结点,所以在B+树中,无论查找成功与否,最终一定都要走到最下面一层结点
B树VSB+树
超纲知识,了解即可
在B+树中,非叶结点不含有该关键字对应记录的存储地址,可以使一个磁盘包含更多个关键字,使B+树的阶更大,树高更矮,读磁盘个数更少,查找更快(Mysql数据库的底层就是B+树)
6.散列查找(哈希表)
散列表,又称哈希表,是一种数据结构,特点是:数据元素的关键字与其存储地址直接相关,通过哈希函数来建立关键字与存储地址的关系。
哈希表的建立
若不同的关键字通过哈希函数映射到同一个值,则称他们为同义词
通过哈希函数确定的位置已经存放了其他元素,则称这种情况为冲突
用拉链法处理冲突,把所有同义词存储在一个链表中
装填因子α=表中记录数/哈希表长度
哈希表的查找
- 通过哈希函数计算目标元素的存储地址
- 在存储地址存储的链表中寻找目标值
查找长度—在查找运算中,需要对比关键字的次数,不包括哈希值的计算
哈希表的重点:设计一个好的哈希函数,降低冲突数量,加快查找效率
常见的哈希函数
除留余数法——H(key) = key%p
散列表表长为m,取一个不大于m且最接近或等于m的质数p
直接定址法——H(key)=key或H(key)=a*key+b
这种计算方法最简单且不会产生冲突,适合关键字分布基本连续的情况,若关键字分布不连续,空位较多,会造成浪费
数字分析法——选取数码分布较为均匀的若干位作为哈希地址
平方取中法——去关键字的平方值的中间几位作为哈希地址