数据结构-线性表的查找


本篇文章用于记录,不适合参考

查找(一)

9.1 查找基本概念

查找又称为检索,是根据关键字寻找指定元素的一种操作。

查找表也称为被查找对象,是一组元素(或者记录)的集合。

查找表由元素组成,每个元素由若干个数据项组成,这若干个数据项中有一个数据项可以被指定为关键字。每个元素,其关键字的取值唯一。

  • 查找表的分类

查找过程中会对查找表进行修改(如插入或删除),那么这个查找表属于动态查找表。

查找过程中不会对表进行修改,这个查找表属于静态查找表。

  • 查找的分类

内查找:查找过程在内存中进行

外查找:查找过程需要访问外存

  • 平均查找长度(Average Search Length)

意义:衡量查找算法性能好坏的重要指标。 A S L ASL ASL越小,算法时间性能越好,反之,越差。

定义:
A S L = ∑ i = 1 n p i c i ASL = \sum^{n}_{i=1}p_{i}c_{i} ASL=i=1npici
n n n表示查找表中元素的个数, p i p_i pi表示查找第 i i i个元素的概率, c i c_i ci表示找到第 i i i个元素所需的关键字比较次数。通常假设每个元素的查找概率相等,此时 p i p_i pi= 1 n \frac{1}{n} n1

A S L ASL ASL分为查找成功 A S L 成 功 ASL_{成功} ASL和查找失败 A S L 不 成 功 ASL_{不成功} ASL两种情况。

对于 A S L 成 功 ASL_{成功} ASL, p i p_i pi为查找到第 i i i个元素的概率。

对于 A S L 不 成 功 ASL_{不成功} ASL,假设有 m m m种查找失败的情况, p i p_i pi为第 i i i种情况的概率。

9.2 线性表的查找

对于每个元素,其采用如下结构:

typedef int KeyType;
typedef struct{
    KeyType key;
    InfoType date;
}RecType;

9.2.1 顺序查找

顺序查找是最简单的查找方法。

算法:从表的一端向表的另一端逐个元素地查找。

时间复杂度: O ( n ) O(n) O(n)

优点:简单

缺点:效率低, n n n大时不适用

实现:

int SeqSearch(RecType R[], int n, KeyType k){
    int i = 0;
    while(i<n && R[i]!=k){
        ++i;
    }
    if(i>=n){
        return 0;
    }
    else{
        return i+1;
    }
}
  • 算法优化:哨兵

上述算法中可以在顺序表R的末尾添加一个关键字记录k,该记录称为哨兵

算法优化后的实现:

int SeqSearch(RecType R[], int n, KeyType k){
    int i = 0;
    R[n].key = k;
    while(R[i].key!=k){
        ++i;
    }
    if(i==n){
        return 0;
    }
    else{
        return i+1;
    }
}

优化分析:这个优化主要是在while语句的判定条件处,优化后查找过程中不用再判断是否越界。这个优化在查找表规模较大时有明显的效益。

9.2.2 折半查找

算法:顺序表 R [ l o w ] R[low] R[low] R [ h i g h ] R[high] R[high]为当前的查找范围。 m i d = ⌊ ( l o w + h i g h ) / 2 ⌋ mid= \lfloor (low + high)/2 \rfloor mid=(low+high)/2

  1. k = R [ m i d ] . k e y k=R[mid].key k=R[mid].key,查找成功,返回元素逻辑编号。
  2. k < R [ m i d ] k<R[mid] k<R[mid],收缩查找范围, h i g h = m i d − 1 high = mid - 1 high=mid1。再次查找。
  3. k > R [ m i d ] k>R[mid] k>R[mid],收缩查找范围, l o w = m i d + 1 low = mid + 1 low=mid+1。再次查找。

在这里插入图片描述

元素不在查找表中时: l o w > h i g h low > high low>high终止算法。

思路:每一次比较都将查找范围减半。

时间复杂度: O ( l o g 2 n ) O(log_2n) O(log2n)

前提:查找表有序

优点:查找效率高

缺点:由于需要查找表有序,对元素要能够随机读取。故更加适用于顺序表,对于链表的折半查找的实现相对复杂。

9.2.3 索引存储结构

  1. 索引存储结构

索引存储结构就是在存储数据时,额外建立一个索引表。

  • 索引存储结构的优点:提高查找效率。
  • 缺点:建立索引表增加额外的时间和空间开销

索引表中的每个元素称为索引项,每个索引项的一般形式为(关键字,地址)

  1. 分块查找

分块查找是一种性能介于顺序查找和折半查找之间的查找算法。

算法:

将整个表 R [ 0... n − 1 ] R[0...n-1] R[0...n1]均分为 b b b块,前 b − 1 b-1 b1块中各有 s = ⌈ n / b ⌉ s=\lceil n/b \rceil s=n/b个元素,最后一块中含有剩下的元素(最后一块中的元素个数可能小于等于 s s s。每一块中的元素不必有序,但是必须满足,前一块中的最大关键字小于后一块中的最小关键字。即要求整个表是"分块有序"。

抽取各个块中的最大关键字及其起始位置(对应地址)作为索引项存入索引表中。索引表按关键字有序。

在这里插入图片描述

实现:

索引表的数据类型声明:

#define MAXI <索引表的最大长度>
typedef struct{
    KeyType key; //KeyType为关键字的类型
    int link; //指向对应块的起始下标
} IdxType;

实现:使用二分查找检索索引表,使用顺序查找检索块。

int IdxSearch(IdxType I[], int b, RecType R[], int n, KeyType k){
    int s = (n+b-1)/b;
    int low = 0, high = b-1, mid, i;
    while(low <= high){ //在索引表中查找目标区块,使用折半查找
        mid = (low + high)/2;
        if(I[mid].key >= k){ //这里if中的判断条件与折半查找还是有区别的,需要注意。
                             //因为这里需要找到的是某个区块,而不是某个具体元素。
            high = mid-1;
        }else{
            low = mid + 1;
        }
    }
    //下一步,在主数据表(目标区块)中使用顺序查找
    i = I[high + 1].link;
    while(i<=I[high + 1].link + s-1 && R[i].key!=k){
        ++i;
    }
    if(i<=I[high + 1].link + s-1){
        return i+1;
    }else{
        return 0; //查找失败
    }
    
}
  • 检索索引表时使用二分查找还是顺序查找?

分块查找中每个块不要求有序,所以一般不能使用二分查找。但是索引表是有序的,这就可以使用二分查找或者顺序查找。那哪种方法更好呢?

根据其 A S L ASL ASL得到结果,这里直接给出结果,不做分析。

若有 n n n个元素,每块中有 s s s个元素(R中总块数 b = ⌈ n s ⌉ b=\lceil \frac{n}{s}\rceil b=sn)。


折半查找确定块时,分块查找成功时的 A S L ASL ASL为:
A S L b l k = l o g 2 ( b + 1 ) − 1 + s + 1 2 ≈ l o g 2 ( n / s + 1 ) + s 2 ASL_{blk} = log_2(b+1) -1+\frac{s+1}{2} \approx log_2(n/s + 1)+\frac{s}{2} ASLblk=log2(b+1)1+2s+1log2(n/s+1)+2s

显然,当 s s s越小时,折半查找更好


顺序查找确定块时,分块查找成功时的 A S L ASL ASL为:
A S L b l k = 1 2 ( n s + s ) + 1 ASL_{blk} = \frac{1}{2}(\frac{n}{s}+s)+1 ASLblk=21(sn+s)+1
显然,当 s = n s=\sqrt{n} s=n 时, A S L b l k ASL_{blk} ASLblk取极小值 n + 1 \sqrt{n}+1 n +1。此时顺序查找更好。

参考资料

[1] 李春葆.数据结构教程(第五版).清华大学出版社.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值