对于线性结构的查找,应掌握其查找的过程、构造判定树、分析平均查找长度等。
一、顺序查找
顺序查找又称线性查找,它对顺序表和链表都是适用的。对于顺序表,可通过数组下标递增来顺序扫描每个元素;对于链表,可通过指针 next 来依次扫描每个元素。
顺序查找通常分为对一般的无序线性表的顺序查找和对按关键字有序的线性表的顺序查找。
① 顺序查找 的 缺点 :当 n 较大时,平均查找长度较大,效率低;
② 顺序查找 的 优点 :对数据元素的存储没有要求,顺序存储或链式存储皆可。对表中记录的有序性也没有要求,无论记录是否按关键字有序,均可应用。同时还需注意,对线性的链表只能进行顺序查找。
1. 一般线性表的顺序查找
1)无序顺序查找的基本思想
① 从线性表的一端开始,逐个检查关键字是否满足给定的条件;
② 若查找到某个元素的关键字满足给定条件,则查找成功,返回该元素在线性表中的位置;
③ 若已经查找到表的另一端,但还没有查找到符合给定条件的元素,则返回查找失败的信息。
2)无序顺序查找的算法代码
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
}
【引入“哨兵”可以避免很多不必要的判断语句,从而提高程序效率。】
3)无序顺序查找成功与失败的平均查找长度
平均查找长度:在查找过程中,一次查找的长度是指需要比较的关键字次数,而平均查找长度则是所有查找过程中进行关键字的比较次数的平均值。平均查找长度是衡量查找算法效率的最主要的指标。
对于有 n 个无序元素的线性表,假设每个元素的查找概率相等:
① 查找成功时,ASL成功 = (1 + 2 + … + n) / n = (n + 1) / 2;
② 查找不成功时,与表中各关键字的比较次数显然是 n + 1 次,即 ASL不成功 = n + 1 。
为什么查找不成功时,比较次数是 n + 1,而非 n ?
答:是因为我们在含有 n 个元素的表的表头(或表尾)加入了一个“哨兵”,从而表长变为 n + 1。将“哨兵”的关键字赋值为要查找的元素值,故在加入了“哨兵”的表中查找元素一定会成功(与关键字比较次数最多为 n + 1 次),并返回元素查找成功时所在表中的位置。若返回的位置的值为 0(“哨兵”在表尾:为 n),可以得出查找失败。加入“哨兵”的好处是不需要考虑判断数组下标越界的问题,如果没有“哨兵”,则会产生更多的比较次数。
【通常,查找表中记录的查找概率并不相等。若能预先得知每个记录的查找概率,则应先对记录的查找概率进行排序,使表中记录按查找概率由大至小重新排列。】
2. 有序线性表的顺序查找
1)有序顺序查找的基本思想
若在查找之前就已经知道表的关键字是有序的,则查找失败时可以不用再比较到表的另一端就能返回查找失败的信息,从而 降低顺序查找失败的平均查找长度 。
假设表 L 是按关键字从小到大排列的,查找的顺序是从前往后,待查找元素的关键字为 key 。当查找到第 i 个元素时,发现第 i 个元素对应的关键字小于 key,但第 i + 1 个元素对应的关键字大于 key,这时就可返回查找失败的信息。因为第 i 个元素之后的元素的关键字均大于 key,所以表中不存在关键字为 key 的元素。
2)有序顺序查找成功与失败的平均查找长度
可以下图所示的 判定树 来描述有序线性表的查找过程。
① 树中的圆形结点表示有序线性表中存在的元素;
② 树中的矩形结点称为失败结点(若有 n 个结点,则有 n + 1 个查找失败结点),它描述的是那些不在表中的数据值的集合。
若查找到失败结点, 则说明查找不成功。
① 在有序线性表的顺序查找中,查找成功的平均查找长度和一般线性表的顺序查找一样。
② 查找失败时,查找指针一定走到了某个失败结点。这些失败结点是我们虚构的空结点,实际上是不存在的,所以到达失败结点时所查找的长度等于它上面的一个圆形结点的所在层数。
假设每个元素的查找概率相等,则查找不成功的平均查找长度为:
ASL不成功 = (1 + 2 + … + n + n) / (n + 1) = (n / 2) + (n / (n + 1)) 。
【注:有序线性表的顺序查找和折半查找的思想是不一样的,且有序线性表的顺序查找中的线性表可以是链式存储结构。】
3. 有关线性查找的例题
① 顺序查找适合于存储结构为( A )的线性表。
A. 顺序存储结构或链式存储结构
B. 散列存储结构
C. 索引存储结构
D. 压缩存储结构
② 由 n 个数据元素组成的两个表:一个递增有序,一个无序。采用顺序查找算法,对有序表从头开始查找,发现当前元素已不小于待查元素时,停止查找,确定查找不成功,已知查找任意一个元素的概率是相同的,则在两种表中成功查找( B )。
A. 平均时间后者小
B. 平均时间两者相同
C. 平均时间前者小
D. 无法确定
③ 对长度为 3 的顺序表进行查找, 若查找第一个元素的概率为1/2,查找第二个元素的概率为1/3,查找第三个元素的概率为1/6,则查找任意一个元素的平均查找长度为( A )。
A. 5/3
B. 2
C. 7/3
D. 4/3
二、折半查找
折半查找又称二分查找,它仅适用于有序的顺序表。因为折半查找需要方便地定位查找区域,所以它要求线性表必须具有随机存取的特性,因此,该查找法仅适合于顺序存储结构,不适合于链式存储结构,且要求元素按关键字有序排列。
1. 折半查找的基本思想
1)首先将给定值 key 与表中中间位置的元素进行比较,若相等,则查找成功,返回该元素的存储位置;
2)若不等,则所需查找的元素只能在中间元素以外的前半部分或后半部分(例如,在查找表升序排列时,若给定值 key 大于中间元素,则所查找的元素只可能在后半部分)。然后缩小范围继续进行同样的查找。
3)如此重复,直到找到为止,或确定表中不存在所需要查找的元素,则查找不成功,返回查找失败的信息。
2. 折半查找的算法代码
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; //查找失败,返回-1
}
当折半查找算法选取中间结点时,既可以采用向下取整,也可以采用向上取整。但每次查找的取整方式必须相同。
3. 折半查找成功与失败的平均查找长度
折半查找的过程可用下图所示的二叉树来描述,称为 判定树 。
1)树中每个圆形结点表示一个记录,结点中的值为该记录的关键字值;
2)树中的叶结点都是矩形的,它表示查找不成功的情况。
从判定树可以看出,查找成功时的查找长度为从根结点到目的结点的路径上的结点数,而查找不成功时的查找长度为从根结点到对应失败结点的父结点的路径上的结点数。
每个结点值均大于其左子结点值,且均小于其右子结点值。若有序序列有 n 个元素,则对应的判定树有 n 个圆形的非叶结点和 n + 1 个矩形的叶结点。显然,判定树是一棵 平衡二叉树 。
假设每个元素的查找概率相等,则上图所示的判定树中:
ASL成功 = (1 × 1 + 2 × 2 + 3 × 4 + 4 × 4) / 11 = 33 / 11 = 3;
ASL失败 = (3 × 4 + 4 × 8) / 12 = 44 / 12 = 11 / 3。
4. 有关折半查找的例题
① 【2010 统考真题】已知一个长度为16 的顺序表 L,其元素按关键字有序排列,若采用折半查找法查找一个 L 中不存在的元素,则关键字的比较次数最多是( B )。
A. 4
B. 5
C. 6
D. 7
② 【2015 统考真题】下列选项中,不能构成折半查找中关键字比较序列的是( A )。
A. 500, 200, 450, 180
B. 500, 450, 200, 180
C. 180, 500, 200, 450
D. 180, 200, 500, 450
③ 【2017 统考真题】下列二叉树中,可能成为折半查找判定树(不含外部结点)的是( A )。
④ 【2023 统考真题】对含 600 个元素的有序顺序表进行折半查找,关键字间的比较次数最多是( B )。
A. 9
B. 10
C. 30
D. 300
三、分块查找
分块查找又称索引顺序查找,它吸取了顺序查找和折半查找各自的优点,既有动态结构,又适于快速查找。
1. 分块查找的基本思想
将查找表分为若干子块。块内的元素可以无序,但块间的元素是有序的 。即第一个块中的最大关键字小于第二个块中的所有记录的关键字,第二个块中的最大关键字小于第三个块中的所有记录的关键字,以此类推。再建立一个索引表,索引表中的每个元素含有各块的最大关键字和各块中的第一个元素的地址,索引表按关键字有序排列。
分块查找的过程分为两步:
第一步是在索引表中确定待查记录所在的块,可以顺序查找或折半查找索引表;
第二步是在块内顺序查找。
例如,关键码集合为 {88, 24, 72, 61, 21, 6, 32, 11, 8, 31, 22, 83, 78, 54} ,按照关键码值 24, 54, 78, 88,分为 4 个块和索引表,如下图所示:
2. 分块查找成功与失败的平均查找长度
分块查找的平均查找长度为索引查找和块内查找的平均长度之和。
设索引查找和块内查找的平均查找长度分别为 LI 和 LS,则分块查找的平均查找长度为:
ASL = LI + LS
将长度为 n 的查找表均匀地分为 b 块,每块有 s 个记录,在等概率情况下,若在块内和索引表中均采用顺序查找,则平均查找长度为:
ASL = LI + LS = (b + 1) / 2 + (s + 1) / 2 = (s2 + 2s + n) / 2s 。
此时,若 s = n1/2,则平均查找长度取最小值 n1/2 + 1 。
虽然索引表占用了额外的存储空间,索引查找也增加了一定的系统开销,但由于其分块结构,使得在块内查找时的范围较小,因此与顺序查找相比,分块查找的总体效率提升了不少。
3. 有关分块查找的例题
① 采用分块查找时,数据的组织方式为( B )。
A. 数据分成若干块,每块内数据有序
B. 数据分成若干块,每块内数据不必有序,但块间必须有序,每块内最大(或最小)的数据组成索引块
C. 数据分成若干块,每块内数据有序,每块内最大(或最小)的数据组成索引块
D. 数据分成若干块,每块(除最后一块外)中数据个数需相同
② 对有 2500 个记录的索引顺序表(分块表)进行查找,最理想的块长为( A )。
A. 50
B. 125
C. 500
D. ⌈log22500⌉
③ 设顺序存储的某线性表共有 123 个元素,按分块查找的要求等分为 3 块。若对索引表采用顺序查找法来确定子块,且在确定的子块中也采用顺序查找法,则在等概率情况下,分块查找成功的平均查找长度为( B )。
A. 21
B. 23
C. 41
D. 62
④ 为提高查找效率,对有 65025 个元素的有序顺序表建立索引顺序结构,在最好情况下查找到表中已有元素最多需要执行( C )次关键字比较。
A. 10
B. 14
C. 16
D. 21