26考研——查找_顺序查找、折半查找和分块查找(7)

408答疑



二、顺序查找、折半查找和分块查找

顺序查找

顺序查找概述

顺序查找,也称为线性查找,是一种基础的查找方法,适用于顺序表和链表这两种数据结构。

顺序查找在顺序表中的应用

在顺序表中,可以通过数组下标递增的方式来顺序扫描每个元素。

顺序查找在链表中的应用

在链表中,可以通过指针 next 来依次扫描每个元素。

顺序查找的分类

顺序查找通常分为两种类型:

  1. 一般的无序线性表的顺序查找
  2. 对按关键字有序的线性表的顺序查找

下面将分别对这两种情况进行讨论。

一般线性表的顺序查找

一般线性表的顺序查找是一种简单的查找方法,其基本思想是从线性表的一端开始,逐个检查关键字是否满足给定条件。

实现方法

顺序查找的实现方法如下:

  1. 从线性表的一端开始,逐个检查每个元素的关键字。
  2. 若找到某个元素的关键字满足给定条件,则查找成功,返回该元素在线性表中的位置。
  3. 若已经查找到表的另一端,但还没有找到符合条件的元素,则返回查找失败的信息。
查找成功时的平均长度计算

对于有 n n n 个元素的表,给定值 key 与表中第 i i i 个元素相等,即定位第 i i i 个元素时,需进行 n − i + 1 n-i+1 ni+1 次关键字的比较,即 C i = n − i + 1 C_i = n-i+1 Ci=ni+1。查找成功时,顺序查找的平均长度为:

ASL 成功 = ∑ i = 1 n P i ( n − i + 1 ) \text{ASL}_{\text{成功}} = \sum_{i=1}^{n} P_i (n - i + 1) ASL成功=i=1nPi(ni+1)

当所有元素的查找概率相等时(即 P i = 1 n P_i = \frac{1}{n} Pi=n1),平均查找长度简化为:
ASL 成功 = n + 1 2 \text{ASL}_{\text{成功}} = \frac{n + 1}{2} ASL成功=2n+1

查找不成功时的平均长度计算

查找不成功时,与表中各关键字的比较次数显然是 n + 1 n+1 n+1 次,即 ASL 不成功 = n + 1 \text{ASL}_{\text{不成功}} = n+1 ASL不成功=n+1

代码实操
typedef struct{ // 查找表的数据结构(顺序表)
    ElemType *elem; // 动态数组基址
    int TableLen;    // 表的长度
}SSTable;

int Search_Seq(SSTable ST, ElemType key) { // “哨兵”
    ST.elem[0] = key; // 将ST.elem[0]称为哨兵,引入它的目的是使得Search_Seq内的循环不必判断数组是否会越界。
    for(int i = ST.TableLen; ST.elem[i] != key; --i); // 从后往前找
    return i; // 若查找成功,则返回元素下标;若查找失败,则返回0
}

顺序查找算法的基本思想是从线性表的一端开始,逐个检查关键字是否满足给定条件。

  • 哨兵的概念:在顺序查找算法中,引入了一个称为“哨兵”的元素,其目的是简化查找过程,避免在每次比较时都需要检查是否越界。
  • 哨兵的作用:通过将哨兵元素放置在数组的第一个位置,算法可以从数组的末尾开始向前查找,直到找到目标元素或遇到哨兵。这样可以减少边界检查的需求,从而提高查找效率。
  • 查找过程:算法从后往前查找,如果找到与给定关键字相等的元素,则返回该元素的索引;如果遍历到哨兵位置仍未找到,则返回 0,表示查找失败。

通过引入哨兵,可以避免很多不必要的判断语句,从而提高程序的执行效率。

总结

顺序查找的缺点是当 n n n 较大时,平均查找长度较大,效率低;优点是对数据元素的存储没有要求,顺序存储或链式存储皆可。对表中记录的有序性也没有要求,无论记录是否按关键字有序,均可应用。同时还需注意,对链表只能进行顺序查找。

有序线性表的顺序查找

有序线性表的顺序查找利用了表中元素有序的特性,通过比较关键字与元素的大小关系,可以提前终止查找过程,从而降低查找长度,提高查找效率。

有序表的查找失败优化
  • 在查找之前已知表是关键字有序的,可以优化查找失败的处理。
  • 查找失败时,不需要比较到表的另一端即可返回查找失败的信息。这样可以降低查找失败的平均查找长度。
实现方法

在有序线性表中进行顺序查找时,可以采取以下策略:

  1. 从第一个元素开始:从表的第一个元素开始,逐个比较关键字与元素的大小。
  2. 利用有序特性:如果当前元素的关键字大于目标关键字,则可以停止查找,因为后续元素的关键字只会更大。
  3. 查找成功:如果找到与目标关键字相等的元素,则返回该元素的位置。
  4. 查找失败:如果遍历完整个表都没有找到,则返回一个特定的值(如-1),表示查找失败。
判定树结构
  • 判定树用于描述有序线性表的查找过程:
    • 圆形结点 表示表中存在的元素;
    • 矩形结点(失败结点) 表示不在表中的数据集合,共有 n + 1 n+1 n+1 个( n n n 为表中元素个数)。
  • 查找失败时,指针会走到某个失败结点,其查找长度等于该失败结点上一层圆形结点的层数

在这里插入图片描述

查找成功的平均查找长度
  • 与一般线性表的顺序查找相同,计算公式为:
    A S L 成功 = 1 n ∑ i = 1 n i = n + 1 2 ASL_{\text{成功}} = \frac{1}{n} \sum_{i=1}^{n} i = \frac{n+1}{2} ASL成功=n1i=1ni=2n+1
查找不成功的平均查找长度
  • 计算公式为:
    A S L 不成功 = ∑ j = 1 n q j ( l j − 1 ) ASL_{\text{不成功}} = \sum_{j=1}^{n} q_j (l_j - 1) ASL不成功=j=1nqj(lj1)
    其中:
    • q j q_j qj 为到达第 j j j 个失败结点的概率,在等概率情况下 q j = 1 n + 1 q_j = \frac{1}{n+1} qj=n+11
    • l j l_j lj 为第 j j j 个失败结点所在的层数。
  • 化简后为:
    A S L 不成功 = 1 + 2 + ⋯ + n + n n + 1 = n 2 + n n + 1 ASL_{\text{不成功}} = \frac{1 + 2 + \cdots + n + n}{n + 1} = \frac{n}{2} + \frac{n}{n + 1} ASL不成功=n+11+2++n+n=2n+n+1n
  • 示例:当 n = 6 n=6 n=6 时,
    A S L 不成功 = 6 2 + 6 7 = 3.86 ASL_{\text{不成功}} = \frac{6}{2} + \frac{6}{7} = 3.86 ASL不成功=26+76=3.86

折半查找

定义

折半查找也称二分查找,它仅适用于有序的顺序表。

基本思想

  • 首先将给定值 k e y key key 与表中中间位置的元素比较,若相等,则查找成功,返回该元素的存储位置。
  • 若不等,则所需查找的元素只能在中间元素以外的前半部分或后半部分(例如,在查找表升序排列时,若 k e y key key 大于中间元素,则所查找的元素只可能在后半部分),然后在缩小的范围内继续进行同样的查找。
  • 重复上述步骤,直到找到为止,或确定表中没有所需要查找的元素,则查找不成功,返回查找失败的信息。

代码实操

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
}
  1. 初始化:设置两个指针,一个指向数组的起始位置(low),一个指向数组的末尾位置(high)。
  2. 查找循环:在low不大于high时,进行循环。
  3. 计算中间位置:计算中间位置mid = (low + high) / 2。
  4. 比较与调整
    • 如果中间位置的元素等于目标值(key),则返回该位置mid。
    • 如果中间位置的元素大于目标值,说明目标值在数组的前半部分,因此将high更新为mid-1。
    • 如果中间位置的元素小于目标值,说明目标值在数组的后半部分,因此将low更新为mid+1。
  5. 查找失败:如果low大于high,说明数组中不存在目标值,返回-1。

当折半查找算法选取中间结点时,既可以采用向下取整,又可以采用向上取整。但每次查找的取整方式必须相同!!!

查找路径的判断

  • 已知 11 个元素的有序表 {7, 10, 13, 16, 19, 29, 32, 33, 37, 41, 43},要查找值为 11 和 32 的元素。
  • 指针 low 和 high 分别指向表的下界和上界,mid 则指向表的中间位置: m i d = ⌊ l o w + h i g h 2 ⌋ mid = \left\lfloor \frac{low + high}{2} \right\rfloor mid=2low+high
查找11的过程

在这里插入图片描述

  1. 第一次查找
    • l o w = 0 low=0 low=0, h i g h = 10 high=10 high=10, m i d = 5 mid=5 mid=5(元素29)
    • 比较: 11 < 29 11 < 29 11<29,调整范围至 [ l o w , m i d − 1 ] [low, mid-1] [low,mid1]
    • 更新指针: h i g h = 4 high=4 high=4, m i d = 2 mid=2 mid=2

在这里插入图片描述

  1. 第二次查找
    • l o w = 0 low=0 low=0, h i g h = 4 high=4 high=4, m i d = 2 mid=2 mid=2(元素13)
    • 比较: 11 < 13 11 < 13 11<13,调整范围至 [ l o w , m i d − 1 ] [low, mid-1] [low,mid1]
    • 更新指针: h i g h = 1 high=1 high=1, m i d = 0 mid=0 mid=0

在这里插入图片描述

  1. 第三次查找
    • l o w = 0 low=0 low=0, h i g h = 1 high=1 high=1, m i d = 0 mid=0 mid=0(元素7)
    • 比较: 11 > 7 11 > 7 11>7,调整范围至 [ m i d + 1 , h i g h ] [mid+1, high] [mid+1,high]
    • 更新指针: l o w = 1 low=1 low=1, m i d = 1 mid=1 mid=1

在这里插入图片描述

  1. 第四次查找
    • l o w = 1 low=1 low=1, h i g h = 1 high=1 high=1, m i d = 1 mid=1 mid=1(元素10)
    • 比较: 11 > 10 11 > 10 11>10,调整范围至 [ m i d + 1 , h i g h ] [mid+1, high] [mid+1,high]
    • 更新指针: l o w = 2 low=2 low=2, l o w > h i g h low>high low>high,退出循环,查找失败

判定树

折半查找的过程可用下图所示的二叉树来描述,称为判定树。

在这里插入图片描述

  • 树中每个圆形结点表示一个记录,结点中的值为该记录的关键字值;树中最下面的叶结点都是方形的,它表示查找失败的区间。
  • 从判定树可以看出,查找成功时的查找长度为从根结点到目的结点的路径上的结点数,而查找失败时的查找长度为从根结点到对应失败结点的父结点的路径上的结点数。
  • 每个结点值均大于其左子结点值,且均小于其右子结点值。

最多比较次数的分析

  • 用折半查找法查找到给定值的比较次数最多不会超过树的高度。
  • 在等概率查找时,查找成功的平均查找长度为:
    A S L = 1 n ∑ i = 1 n l i = 1 n ( 1 × 1 + 2 × 2 + ⋯ + h × 2 h − 1 ) = n + 1 n log ⁡ 2 ( n + 1 ) − 1 ≈ log ⁡ 2 ( n + 1 ) − 1 ASL = \frac{1}{n} \sum_{i=1}^{n} l_i = \frac{1}{n} (1 \times 1 + 2 \times 2 + \cdots + h \times 2^{h-1}) = \frac{n + 1}{n} \log_2(n + 1) - 1 \approx \log_2(n + 1) - 1 ASL=n1i=1nli=n1(1×1+2×2++h×2h1)=nn+1log2(n+1)1log2(n+1)1
    其中, h h h 是树的高度,并且元素个数为 n n n 时树高 h = ⌈ log ⁡ 2 ( n + 1 ) ⌉ h = \lceil \log_2(n + 1) \rceil h=log2(n+1)⌉
  • 所以,折半查找的时间复杂度为 O ( log ⁡ 2 n ) O(\log_2 n) O(log2n),平均情况下比顺序查找的效率高。

适用场景

  • 因为折半查找需要方便地定位查找区域,所以它要求线性表必须具有随机存取的特性。
  • 因此,该查找仅适合于顺序存储结构,不适合于链式存储结构,且要求元素按关键字有序排列。
  • 二分查找可以使用一棵二叉树来描述,这棵树称为判定树,且为一棵高度平衡的二叉树。

有序线性表的顺序查找与折半查找的区别

  1. 思想不同
    • 有序线性表的顺序查找仍基于顺序比较;
    • 折半查找基于分治策略。
  2. 存储结构限制
    • 有序线性表的顺序查找支持链式存储
    • 折半查找仅支持顺序存储
  3. 性能分析
    • 有序线性表的顺序查找在失败时的平均性能优于一般顺序查找,但仍弱于折半查找。

分块查找

定义与基本思想

  • 分块查找也称索引顺序查找,它吸取了顺序查找和折半查找各自的优点,既有动态结构,又适于快速查找。
  • 基本思想是将查找表分为若干子块。块内的元素可以无序,但块间的元素是有序的,即第一个块中的最大关键字小于第二个块中的所有记录的关键字,第二个块中的最大关键字小于第三个块中的所有记录的关键字,以此类推。
  • 再建立一个索引表,索引表中的每个元素含有各块的最大关键字和各块中的第一个元素的地址,索引表按关键字有序排列。

查找过程

  • 分块查找的过程分为两步:
    1. 第一步是在索引表中确定待查记录所在的块,可以顺序查找或折半查找索引表;
    2. 第二步是在块内顺序查找。
示例
  • 例如,关键码集合为 {88, 24, 72, 61, 21, 6, 32, 11, 8, 31, 22, 83, 78, 54};按照关键码值 24, 54, 78, 88,分为 4 个块和索引表,如下图所示。

在这里插入图片描述

平均查找长度

  • 分块查找的平均查找长度为索引查找和块内查找的平均长度之和。设索引查找和块内查找的平均查找长度分别为 L i L_i Li L s L_s Ls,则分块查找的平均查找长度为 A S L = L i + L s ASL = L_i + L_s ASL=Li+Ls

最优块大小

  • 将长度为 n n n 的查找表均匀地分为 b b b 块,每块有 s s s 个记录,在等概率情况下,若在块内和索引表中均采用顺序查找,则平均查找长度为
    A S L = L i + L s = b + 1 2 + s + 1 2 = s 2 + 2 s + n 2 s ASL = L_i + L_s = \frac{b + 1}{2} + \frac{s + 1}{2} = \frac{s^2 + 2s + n}{2s} ASL=Li+Ls=2b+1+2s+1=2ss2+2s+n
  • 此时,若 s = n s = \sqrt{n} s=n ,则平均查找长度取最小值 n + 1 \sqrt{n} + 1 n +1

效率分析

  • 虽然索引表占用了额外的存储空间,索引查找也增加了一定的系统开销,但由于其分块结构,使得在块内查找时的范围较小,因此与顺序查找相比,分块查找的总体效率提升了不少。

六、参考资料

鲍鱼科技课件

b站免费王道课后题讲解:
在这里插入图片描述

网课全程班:
在这里插入图片描述

26王道考研书

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

408答疑+v:18675660929

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值