【数据结构与算法】顺序查找、折半查找、分块查找

顺序查找

顺序查找,又叫线性查找。适用于线性表。它的核心思路是从线性表的一端开始,逐个检查关键字是否满足给定条件。若满足条件,则返回下标。若已经查找到了线性表的另一端,但还没有找到符合给定条件的元素,则返回查找失败。

简述就是,从头到尾扫一遍,有要找的元素就返回下标,没有就返回没有

实现

// C++

struct List
{
    int len;
    int *items;
};

int find(List &list, int aim){
    for(int i = 0; i< list.len; i++){
        if(aim == list.items[i]) return i;
    }
    return -1;
}

对于顺序查找来说,它的执行时间与线性表的长度 n 有关,时间复杂度为 O(n),空间复杂度为 O(1)。

假设使用顺序查找,查找元素在长度为 n 线性表中的位序为 i。那么找到这个元素需要进行 n - i + 1 次关键字的比较。即 C i = n − i + 1 C_i=n-i+1 Ci=ni+1

那么查找成功时,顺序查找的平均查找长度为 A S L = ∑ i = 1 n P i C i = ∑ i = 1 n P i ( n − i + 1 ) ASL=\sum_{i=1}^nP_iC_i=\sum_{i=1}^nP_i(n-i+1) ASL=i=1nPiCi=i=1nPi(ni+1)

若每个元素查找的概率 P i P_i Pi相同,即 P i = 1 / n P_i=1/n Pi=1/n时,有 A S L = ( n + 1 ) / 2 ASL=(n+1)/2 ASL=(n+1)/2

查找不成功时,平均查找长度为 n + 1。

对有序表的顺序查找

如果一个线性表是有序的,那么我们就可以通过比较来提前终止查找,而不是一定要从头到尾扫一遍线性表。

例如:

  • 如果查找元素小于两端元素或大于两端元素,那么我们可以直接返回失败。因为查找元素在线性表元素区间之外。
  • 在查找的过程中,找到了一个比待查找元素大的元素(按从小到大到的顺序遍历)或找到了一个比待查找元素小的元素(按从大到小的顺序遍历),也可以直接返回。

二分查找(折半查找)

二分查找是在对有序表的顺序查找上进行了扩展,但是只适用于适用顺序存储结构的线性表。因为链式结构的线性表无法在 O(1)的时间内读取到元素。

二分查找的核心思路是,不断地划分区间,直至找到元素或区间不可再划分。

例如:

假设我们在有序顺序表 1 3 5 7 9 11 13 15 中查找元素 3。

那么第一次,判断中位数与关键字的大小,中位数为 7(共有 8 个元素,元素下标为 0 到 7,因为计算机中两个整形数相除还是整形数,可能会丢失精度,所以中位数的下标为 ( 0 + 7 ) / 2 = 3 (0+7)/2=3 (0+7)/2=3,3 号位上的元素为 7),3 比 7 小,所以在下标为[0, 3)这个区间内查找,即在[0,2]区间内查找。

第二次,判断中位数与关键字的大小,中位数的位序为 ( 0 + 2 ) / 2 = 1 (0+2)/2=1 (0+2)/2=1,1 位序上的数为 3,等于查找元素。直接返回位序 1。

实现

int binary_search(List &list, int aim)
{
    int left = 0, right = list.len - 1; // 初始的左右区间
    int mid;                            // 中位数

    while (left <= right)
    {
        mid = (left + right) >> 1; // 右移一位,等于(left + right)/2
        if (list.items[mid] == aim)
            return mid;
        else if (list.items[mid] > mid)
            right = mid - 1;
        else
            left = mid + 1;
    }
}

如果使用的是 C++且不想自己手写二分,那么可以直接用 C++标准库中提供的二分函数。(对于考研人来说还是不要用了,因为不能保证改卷老师知道 C++标准库里有二分函数)

#include <algorithm>

int binary_search(List &list, int aim)
{
    int index = std::lower_bound(list.items, list.items + list.len, aim) - list.items;
    if(list.items[index] == aim) return index;
    else return -1;
}

二分查找判定树

二分查找的过程可以用树来表示,这个树称为判定树。

例如:

假设我们在有序顺序表 1 3 5 7 9 11 13 15 中查找元素,那么它的判定树是这样的

1
3
5
7
9
11
13
15

这里有个空结点是因为 markdown 画不出来二叉树的形状,因此这里用个空站位,实际上是没有这个结点的

通过判定树,我们可以直观地了解到,二分查找的比较次数与判定树的高度 h 有关。

由于二分查找的判定树是一棵接近满二叉树的平衡二叉树。结点个数为 n 的满二叉树的树高为 l o g 2 ( n + 1 ) log_2(n+1) log2(n+1)。所以折半查找的时间复杂度近似为 O ( l o g 2 n ) O(log_2n) O(log2n)

分块查找(索引顺序查找)

分块查找结合了顺序查找和二分查找。它的核心是划分区间、添加索引。

当然,如果分区和添加索引是在查找开始时进行的,那么时间开销巨大,因此,分块查找实际上是对一种类似于跳表的数据结构进行的查找。

分块查找要求:

  • 索引的值为每个块中的最大元素,索引指向块中的第一个元素。
  • 索引的值必须是有序的,块内元素的值可以是无序的。

分块查找的过程分为两部:

  1. 使用顺序查找或二分查找在索引表中查找记录所在的块。
  2. 在块内进行顺序查找、

例如:现在有待查找序列 24 , 21 , 6 , 11 , 8 , 22 , 32 , 31 , 54 , 72 , 61 , 78 , 88 , 83 {24,21,6,11,8,22,32,31,54,72,61,78,88,83} 24,21,6,11,8,22,32,31,54,72,61,78,88,83

我们拟建立分块:

区间块内元素索引的值索引
[1,6]24, 21, 6, 11, 8, 22241
[7, 9]32, 31, 54547
[10, 12]72, 61, 787810
[13, 14]88, 838813

在使用分块查找的时候先在索引列表里找到符合条件的区间,再到对应的区间里去找有没有符合条件的值。

假设我们要找32这个元素,那么从索引里找,发现32在 (24, 54]这个区间内。

所以,按照索引,从第7号元素开始执行顺序查找,第7号元素就是32,找到了返回下标。


在现实中,直接使用分块查找算法的情况并不多。但是分块的思想使用得非常多。例如大数据处理的时候,我们常常会将数据按照时间分块来提高处理速度。

最理想的分块情况

将长度为n的查找表均分为b块,每块有s个记录。设分块查询查找索引的平均查找长度为 L i L_i Li,块内查找的平均查找长度为 L s L_s Ls

在等概率的情况下,若在块内和块间均采用顺序查找,则平均查找长度

A S L = L i + L s = ( b + 1 ) / 2 + ( s + 1 ) / 2 = ( s 2 + 2 s + n ) / 2 s = ( s + n / s + 2 ) / 2 ASL = L_i + L_s = (b+1)/2 + (s+1)/2 = (s^2+2s+n)/2s = (s + n/s + 2)/2 ASL=Li+Ls=(b+1)/2+(s+1)/2=(s2+2s+n)/2s=(s+n/s+2)/2

由均值不等式可知,在 s = n s=\sqrt{n} s=n 时, ( s + n / s + 2 ) / 2 (s + n/s + 2)/2 (s+n/s+2)/2 最小。

所以,理想状况下,每个分块内的元素个数应为 s = n s=\sqrt{n} s=n

全篇结束,感谢阅读!

  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然可以!顺序查找折半查找是两种常用的数据结构查找方法。下面是这两种方法的简要介绍: 1. 顺序查找(Sequential Search): 顺序查找也称为线性查找,是一种简单直接的方法。它按照数据元素的逻辑顺序,逐个比较数据元素,直到找到要查找的数据元素为止。顺序查找的时间复杂度为O(n),其中n是数据元素的数量。这种方法适用于数据元素有序的情况,但当数据元素无序时,效率较低。 顺序查找的基本步骤如下: * 从数据结构中的第一个元素开始逐个比较。 * 如果找到了要查找的数据元素,则返回该元素的索引位置。 * 如果遍历完整个数据结构仍未找到,则表示未找到要查找的数据元素。 2. 折半查找(Binary Search): 折半查找也称为二分查找,是一种基于有序数据结构查找方法。它每次将待查找区间一分为二,根据中间位置的数据元素与目标值进行比较,缩小查找范围,直到找到目标值或区间为空为止。折半查找的时间复杂度为O(log n),其中n是待查找区间的长度。这种方法适用于数据元素已排序的情况,特别是对效率要求较高的场景。 折半查找的基本步骤如下: * 假设当前区间的下界为low,上界为high。 * 如果low >= high,表示区间内不存在目标值,查找结束。 * 在区间内找到一个中间位置mid,如果中间位置的数据元素等于目标值,则查找成功。 * 如果中间位置的数据元素小于目标值,则在左半部分继续查找。 * 如果中间位置的数据元素大于目标值,则在右半部分继续查找。 需要注意的是,折半查找的前提是数据元素已经排序。如果数据元素无序或有大量重复元素,则可能无法达到预期的查找效果。 综上所述,顺序查找折半查找各有优缺点,适用于不同的场景。选择哪种方法取决于具体需求和数据结构的特点。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值