文章目录
查找
考纲内容
(一)查找的基本概念
(二)顺序查找法
(三)分块查找法
(四)折半查找法
(五)树形查找
二叉搜索树;平衡二叉树;红黑树
(六)B树及其基本操作、B+树的基本概念
(七)散列(Hash)表
(八)查找算法的分析及应用
知识框架
复习提示
- 本章是考研命题的重点。
- 对于折半查找,应掌握折半查找的过程、构造判定树、分析平均查找长度等。
- 对于二叉排序树、二叉平衡树和红黑树,要了解它们的概念、性质和相关操作等。
- B 树和 B+树是本章的难点。对于B树,考研大纲要求掌握插入、删除和査找的操作过程;
- 对于 B+树,仅要求了解其基本概念和性质。
- 对于散列查找,应掌握散列表的构造、冲突处理方法(各种方法的处理过程)、查找成功和查找失败的平均查找长度、散列查找的特征和性能分析。
1.查找的基本概念
1) 查找。
在数据集合中寻找满足某种条件的数据元素的过程称为查找。
查找的结果一般分为两种:
一是查找成功,即在数据集合中找到了满足条件的数据元素;
二是查找失败。
2) 查找表。
用于查找的数据集合称为查找表,它由同一类型的数据元素(或记录)组成。
对查找表的常见操作有:
①查询符合条件的数据元素;
②插入、删除数据元素。
3) 静态查找表。
若一个查找表的操作只涉及查找操作,则无须动态地修改查找表,此类查找表称为静态查找表。
与此对应,需要动态地插入或删除的查找表称为动态查找表。
适合静态查找表的查找方法有顺序查找、折半查找、散列查找等;
适合动态查找表的查找方法有二叉排序树的查找、散列查找等。
4) 关键字。
数据元素中唯一标识该元素的某个数据项的值,使用基于关键字的查找,查找结果应该是唯一的。
例如,在由一个学生元素构成的数据集合中,学生元素中“学号”这一数据项的值唯一地标识一名学生。
5) 平均查找长度。
在查找过程中,一次查找的长度是指需要比较的关键字次数,
而平均查找长度则是所有查找过程中进行关键字的比较次数的平均值,其数学定义为
式中,n是查找表的长度;
P是查找第i个数据元素的概率,一般认为每个数据元素的查找概率相等,即 Pi= 1/n;
Ci是找到第i个数据元素所需进行的比较次数。
平均查找长度是衡量查找算法效率的最主要的指标。
2.顺序查找和折半查找
2.1顺序查找
顺序查找又称线性查找,它对顺序表和链表都是适用的。
对于顺序表,可通过数组下标递增来顺序扫描每个元素;
对于链表,可通过指针 next 来依次扫描每个元素。
顺序查找通常分为对一般的无序线性表的顺序查找和对按关键字有序的线性表的顺序査找。
下面分别进行讨论。
2.1.1一般线性表的顺序查找
作为一种最直观的查找方法,其基本思想:
① 从线性表的一端开始,逐个检查关键字是否满足给定的条件;
② 若查找到某个元素的关键字满足给定条件,则查找成功,返回该元素在线性表中的位置;
③若已经查找到表的另一端,但还没有查找到符合给定条件的元素,则返回查找失败的信息。
下面给出其算法,后面说明了算法中引入的“哨兵”的作用。
typedef struct{ //查找表的数据结构(顺序表)
ElemType *elem; //动态数组基址
int TableLen; //表的长度
}SSTable;
int Search_Seq(SSTable ST,ElemType key){
ST.elem[0]=key; //“哨兵”
for(int i=ST.Tablelen;ST.elem[i]!=key;--i); //从后往前找
return i; //若查找成功,则返回元素下标;若查找失败,则返回0
}
上述算法中,将 ST.elem[0]称为哨兵,引入它的目的是使得 Search_Seq 内的循环不必判断数组是否会越界。
算法从尾部开始査找,若找到 ST.elem[i]==key 则返回i值,查找成功。
否则一定在查找到 ST.elem[0]==key 时跳出循环,此时返回的是 0,查找失败。
在程序中引入“哨兵”,可以避免很多不必要的判断语句,从而提高程序效率。
对于有n个元素的表,给定值key与表中第i个元素相等,即定位第i个元素时,需进行n-i+1次关键字的比较,即Ci=n-i+1。
查找成功时,顺序查找的平均长度为
当每个元素的查找概率相等,即Pi=1/n时,有
查找不成功时,与表中各关键字的比较次数显然是n+1次,即 ASL(不成功)=n+1。
通常,查找表中记录的查找概率并不相等。
若能预先得知每个记录的查找概率,则应先对记录的查找概率进行排序,使表中记录按查找概率由大至小重新排列。
综上所述,
- 顺序查找的缺点是当n较大时,平均查找长度较大,效率低;
- 优点是对数据元素的存储没有要求,顺序存储或链式存储皆可。
- 对表中记录的有序性也没有要求,无论记录是否按关键字有序,均可应用。
- 同时还需注意,对链表只能进行顺序查找。
2.1.2有序线性表的顺序查找
若在查找之前就已知表是关键字有序的,则查找失败时可以不用再比较到表的另一端就能返回查找失败的信息,从而降低查找失败的平均查找长度。
假设表L是按关键字从小到大排列的,查找的顺序是从前往后,待查找元素的关键字为 key,当查找到第i个元素时,发现第i个元素的关键字小于 key,但第i+1个元素的关键字大于 key,这时就可返回查找失败的信息,因为第i个元素之后的元素的关键字均大于 key,所以表中不存在关键字为 key 的元素。
【命题追踪——有序线性表的顺序查找的应用】
可以用如图 7.1所示的判定树来描述有序线性表的查找过程。
树中的圆形结点表示有序线性表中存在的元素;
矩形结点称为失败结点(若有n个结点,则相应地有n+1个查找失败结点),它描述的是那些不在表中的数据值的集合。
若查找到矩形结点,则说明查找失败。
在有序线性表的顺序查找中,查找成功的平均查找长度和一般线性表的顺序查找一样。
查找失败时,查找指针一定走到了某个失败结点。
这些失败结点是我们虚构的空结点,实际上是不存在的,所以到达失败结点时所查找的长度等于它上面的一个圆形结点的所在层数。
查找不成功的平均查找长度在相等查找概率的情形下为
ASL(不成功)=
式中,是到达第j个失败结点的概率,在相等査找概率的情形下,它为 1/(n+1);
是第j个失败结点所在的层数。当n=6时,ASL(不成功)=6/2+6/7=3.86,比一般的顺序査找好一些。
注意,有序线性表的顺序查找和后面的折半查找的思想是不一样的,
且有序线性表的顺序查找中的线性表可以是链式存储结构,
而折半查找中的线性表只能是顺序存储结构。
2.2折半查找
折半查找又称二分查找,它仅适用于有序的顺序表。
【命题追踪——分析对比给定查找算法与折半查找的效率】
折半查找的基本思想:
① 首先将给定值 key 与表中中间位置的元素比较,若相等,则查找成功,返回该元素的存储位置;
② 若不等,则所需查找的元素只能在中间元素以外的前半部分或后半部分
(例如,在查找表升序排列时,若 key 大于中间元素,则所査找的元素只可能在后半部分),然后在缩小的范围内继续进行同样的查找。
重复上述步骤,直到找到为止,或确定表中没有所需要查找的元素,则查找不成功,返回查找失败的信息。
算法如下:
int Binary_Search(SSTable L,ElemType key){
int low=0,high=L.TableLen-l,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; //查找失败,返回-1
}
当折半查找算法选取中间结点时,既可以采用向下取整,又可以采用向上取整。
但每次查找的取整方式必须相同,这部分内容请读者结合本题部分习题来理解。
【命题追踪——折半查找的查找路径的判断】
例如,已知 11 个元素的有序表{7,10,13,16,19,29,32,33,37,41,43},要查找值为 11 和 32 的元指针 low 和 high 分别指向表的下界和上界,mid 则指向表的中间位置。
下面来说明查找 11 的过程(查找 32 的过程请读者自行分析):
第一次查找时,将中间位置元素与 key 比较。因为11<29,说明待查元素若存在,则必在范围[ low,mid-1 ]内,
令 high 指向位置mid-1,high=mid-1=5,mid=(1+5)/2=3,第二次查找范围为[1,5]
第二次查找时,将中间位置元素与 key 比较。因为 11<13,说明待查元素若存在,则必在范围[ low,mid-1 ]内,
令 high 指向位置mid-1,high=mid-1=2,mid=(1+2)/2=1,第三次查找范围为[1,2]
第三次查找时,将中间位置元素与 key 比较。因为11>7,说明待查元素若存在,则必在范围[ mid+1,high ]内。
令 low=mid+1=2,mid=(2+2)/2=2,第四次査找范围为[2,2]。
第四次查找,此时子表只含有一个元素,且10≠11,所以表中不存在待查元素。
【命题追踪——分析给定二叉树树形能否构成折半查找判定树】
折半查找的过程可用图 7.2 所示的二叉树来描述,称为判定树。
树中每个圆形结点表示一个记录,结点中的值为该记录的关键字值;
树中最下面的叶结点都是方形的,它表示查找失败的区间。
从判定树可以看出,
- 查找成功时的查找长度为从根结点到目的结点的路径上的结点数,
- 而查找失败时的查找长度为从根结点到对应失败结点的父结点的路径上的结点数;
每个结点值均大于其左子结点值,且均小于其右子结点值。
若有序序列有n个元素,则对应的判定树有n个圆形的非叶结点和 n+1个方形的叶结点。
显然,判定树是一棵平衡二叉树(见后续章节)。
【命题追踪——折半查找的最多比较次数的分析】
由上述分析可知,用折半查找法查找到给定值的比较次数最多不会超过树的高度。
在等概率查找时,查找成功的平均查找长度为
式中,h是树的高度,并且元素个数为n时树高。
所以,折半查找的时间复杂度为,平均情况下比顺序查找的效率高。
在图 7.2 所示的判定树中,在等概率情况下,査找成功(圆形结点)的 ASL=(1x1+2x2+3x4+4x4)/11=3,査找失败(方形结点)的ASL=(3x4+4x8)/12=11/3。
因为折半查找需要方便地定位查找区域,所以它要求线性表必须具有随机存取的特性。
因此,该查找法仅适合于顺序存储结构,不适合于链式存储结构,且要求元素按关键字有序排列。
2.3分块查找
分块查找又称索引顺序查找,它吸取了顺序查找和折半查找各自的优点,既有动态结构,又适于快速查找。
分块查找的基本思想:
- 将查找表分为若干子块。块内的元素可以无序,但块间的元素是有序的,
- 即第一个块中的最大关键字小于第二个块中的所有记录的关键字,
- 第二个块中的最大关键字小于第三个块中的所有记录的关键字,以此类推。
- 再建立一个索引表,索引表中的每个元素含有各块的最大关键字和各块中的第一个元素的地址,索引表按关键字有序排列。
分块查找的过程分为两步:
- 第一步是在索引表中确定待查记录所在的块,可以顺序查找或折半查找索引表;
- 第二步是在块内顺序查找。
例如,关键码集合为{88,24,72,61,21,6,32,11,8,31,22,83,78,54},按照关键码值24,54,78,88,分为4个块和索引表,如图7.3 所示。
分块查找的平均查找长度为索引查找和块内查找的平均长度之和。
设索引查找和块内查找的平均查找长度分别为和,则分块查找的平均查找长度为
将长度为n的查找表均匀地分为b块,每块有s个记录,在等概率情况下,若在块内和索引表中均采用顺序查找,则平均查找长度为
此时,若s=√n ,则平均査找长度取最小值。
虽然索引表占用了额外的存储空间,索引査找也增加了一定的系统开销,但由于其分块结构,使得在块内查找时的范围较小,因此与顺序查找相比,分块查找的总体效率提升了不少。
3.知识回顾