如何更快速地查找

本文涉及的产品
简介: 查找算法在计算机程序设计中占据着主要的核心位置,查找算法的效率直接影响着计算机程序设计与开发的结果与速度。本章主要会讲到顺序查找、二分查找、索引查找和哈希查找这四种查找算法以及效率分析。掌握了相关查找算法,不管是在代码编程计算机技术上面,还在日常生活中都会有很大的用处。


一、概述



查找算法在计算机程序设计中占据着主要的核心位置,查找算法的效率直接影响着计算机程序设计与开发的结果与速度。本章主要会讲到顺序查找、二分查找、索引查找和哈希查找这四种查找算法以及效率分析。掌握了相关查找算法,不管是在代码编程计算机技术上面,还在日常生活中都会有很大的用处。

在分析查找算法的效率时,我们不仅会从空间复杂度和时间复杂度上进行分析,还会使用到平均查找长度(Average Search Length,ASL)来分析,平均查找长度为:

ASL = P1C1 + P2C2 + ... + PnCn

其中,Pi 为查找第 i 个元素的概率,Ci 为第 i 个元素需要比较的次数。


二、顺序查找



顺序查找是指从表中指定位置开始,沿某个方向将记录的关键字与给定值比较,若某个记录的关键字和给定值相等,则查找成功,反之,若找完整个表没有找到与给定值相等的记录,则查找失败。简单的讲,顺序查找就是从头到尾按顺序查找。

微信图片9.gif

简单实现代码如下:

public static int iterationSearch(int[] array, int a) {
    for (int i = 0; i < array.length; i++) {
        if (a == array[i]) {
            return i;
        }
    }
    return -1;
}

从前面可知,顺序查找的空间复杂度为 O(1),时间复杂度最好情况为 O(1),最坏情况为 O(n),平均情况为 O(n),对于具有 n 个记录的顺序表,查找成功时的ASL为:

ASL = ∑ PiCi

对于顺序表中 Ci = n - i + 1,假设查找每个记录的概率相等,即 Pi = 1/n ,则可以得出:

ASL = 1/n ∑ (n - i + 1)  = (n + 1)/2


三、二分查找



如果顺序表的记录是有序的,则可以使用二分查找算法来查找,即每次与表的中间记录进行比较,若相等,则查找成功,若小于中间值,则只可能在表的前半部分,若大于中间值,则只可能在表的后半部分,这样下去,每比较一次,查找范围会缩小一半,直到查找到记录或记录不在表中。微信图片8.gif

简单代码实现如下:

public static int binarySearch(int[] array, int a) {
    int start = 0;
    int end = array.length - 1;
    int mid;
    while (start <= end) {
        mid = (start + end) / 2;
        if (array[mid] < a) {
            start = mid + 1;
        } else if (array[mid] > a) {
            end = mid - 1;
        } else {
            return mid;
        }
    }
    return -1;
}

二分查找的时间复杂度为 O(log n),假设查找概率相等,可以得出:

ASL = 1/n ∑ Ci = 1/n (∑ j × 2 j-1) = (n + 1)/n log2(n + 1) - 1

当 n 很大时,可以得出近似结果:

              ASL ≈ log2(n + 1) - 1

所以我们可以得出,在有序表里使用二分查找的效率是很高的。


四、索引查找



当我们数据量很大且无序时,如何设计查找算法才能更快的查找到数据呢?就像我们查字典一样,如果不看前面的目录,而一页一页的去翻(顺序查找),那是多么可怕。当数据太多,而又杂乱无章的时候,可以考虑使用建立索引来进行查找,一般使用索引的方法的步骤是:

  • 先分析数据规律,建立索引
  • 再根据索引进行快速定位
  • 在定位的地方进行细致搜索

在根据索引查找表时,定位操作不会去匹配整行数据,而是只匹配某一个或几个列的值,假设我们只匹配一个列来确定记录的位置,把这个列称为关键字(Key),后面我们列举两种常见的索引类型来由浅入深地讲解原理和使用。


1. 稠密索引


如果我们要通过关键字来查询数据记录,可以建立一个索引表,在该索引表中只维护原数据表中的关键字,并排列有序,每条索引表的记录中存在一个指向原数据表记录的指针,这样子做的话,当我们需要通过关键字来查询记录时,可以先从索引表中进行查找,找到该数据的索引记录,然后通过指针找到数据表中的记录,因为索引表是有序的,所以查询效率很高(对有序表进行二分查找)。

在前面创建的索引表中,数据表中的每条记录都对应有一个索引记录,这种索引我们把它叫做稠密索引

微信图片7.png

2. 稀疏索引


在使用稠密索引时,每一条数据记录都需要对应存在索引记录,所以需要维护一份与数据表大小相等的索引表,而且为了减少对无效数据IO的操作,我们可以把数据表中的数据分成若干个块,而且要保证块之间是有序的,块内的数据可以无序,然后我们在索引表中关键字只记录每个块中的最大值,指针指向每个块中第一个元素。如下图,比如我们需要查找关键字为18的记录,步骤如下:

  • 从索引表中定位块,因为有 12<18<=26 所以它有可能存在于第2块中
  • 从第2块中查找数据,最终查找到18的位置

这样创建的索引表,索引记录对应数据表中的一个块,这种索引叫做稀疏索引

微信图片6.png在块之间是有序的,所有我们可以对索引表通过顺序查找或二分查找来确定块的位置,然而块内数据是无序的,所以可以通过顺序查找或者哈希查找来定位数据记录,不管是使用什么方式查找,可以得知索引查找的效率与索引表和块内的查找方式有关,因此可知:

ASL = ASL(索引表) + ASL(块内)

再扩展一下我们的思维,如果把索引表再分块,按照前面所讲的方法给索引表再建立索引表,这样就会得到多层的稀疏索引,而且可以通过上层的索引表来定位下一层索引表,以此类推,最终查找到记录。


微信图片5.png

由多层索引表会构建成的整个索引表的树(关于树的数据结构后面会讲到),我们把最顶层的索引称作整个索引树的根(Root),此时树上的非叶子结点只存储索引,而叶子结点存储数据,假设我们按照一定规则建立索引表,在插入或者删除每一条记录时,索引树都会实现自动平衡,而且每个叶子结点都有相邻子叶子结点的指针,这就是传说中的B+树。

微信图片4.png

五、哈希查找



前面讲了顺序查找、二分查找和索引查找,现在来讲一种查询效率更高的算法,一般情况下,在关键字(key)与记录在表中的存储位置之间建立一个函数关系,以函数 H(key) 作为关键字 key 的记录在表中的位置,我们把这个函数叫做哈希函数。而当我们需要通过关键字查找记录时,只需要通过哈希函数做逆计算来取得记录的存储位置,时间复杂度为 O(1)

微信图片3.png

我们在使用哈希查找时,会存在不同的关键字使用哈希函数计算生成的地址会一样,这要两条记录的哈希地址就会产生“冲突”,即当 key1!=key2H(key1)==H(key2),我们把这种现象叫做哈希碰撞,因为很难找到一个不产生冲突的哈希函数,只能使冲突尽可能少。因此,设计一个好的哈希函数需要做到:计算简单、冲突少


1. 哈希函数


例如我们存在一个解放后每年出生人数的统计表,把出生年份当作关键字,然后使用下面哈希函数计算出哈希地址:

H(key) = key + (-1948)

此时表对应哈希地址如下:

微信图片2.png

我们在使用哈希查找时,会存在不同的关键字使用哈希函数计算生成的地址会一样,这要两条记录的哈希地址就会产生“冲突”,即当 key1!=key2H(key1)==H(key2),我们把这种现象叫做哈希碰撞,因为很难找到一个不产生冲突的哈希函数,只能使冲突尽可能少。因此,设计一个好的哈希函数需要做到:计算简单、冲突少


1. 哈希函数

例如我们存在一个解放后每年出生人数的统计表,把出生年份当作关键字,然后使用下面哈希函数计算出哈希地址:

H(key) = key + (-1948)

此时表对应哈希地址如下:

微信图片1.png常见解决冲突的方法有:开放地址法、再哈希法、链地址法、公共溢出区法等,其他方法读者可以自己找资料扩展。


六、总结



四种查找算法对比


算法 顺序查找 二分查找 索引查找 哈希查找
结构 有序/无序 有序 块间有序 有序/无序
存储 顺序/链式 顺序 顺序/链式 顺序/链式
ASL (n+1)/2 log2(n+1)-1 ASL(块间)+ASL(块内) 具体计算
时间复杂度 O(n) O(log ) 块间+块内


数据库中B+树索引与哈希索引的区别:


根据前面的分析可以得到,B+树更适合范围查找,而且支持多列联合索引的最左匹配规则。哈希索引更适合等值查找,但不支持范围查找,也没办法利用索引排序,在存在大量重复键值的情况下,哈希索引效率也是极低的,因为要处理哈希碰撞问题。

相关实践学习
基于函数计算一键部署掌上游戏机
本场景介绍如何使用阿里云计算服务命令快速搭建一个掌上游戏机。
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
目录
相关文章
|
1月前
查找数据
查找数据。
11 1
|
8月前
|
算法
查找
查找是指在图中寻找特定的节点或边的过程。在图中进行查找操作可以帮助我们找到与目标节点或边相关的信息,或者判断图中是否存在某个节点或边。 在图中进行查找操作的常见算法有: 1. 深度优先搜索(DFS):从图中的一个节点开始,沿着一条路径一直深入直到无法再深入为止,然后回溯到上一个节点,继续深入其他路径,直到找到目标节点或遍历完所有节点。 2. 广度优先搜索(BFS):从图中的一个节点开始,先访问它的所有邻居节点,然后再依次访问邻居的邻居节点,直到找到目标节点或遍历完所有节点。 3. Dijkstra算法:用于在带权有向图中找到从一个节点到其他节点的最短路径。该算法通过不断更新节点的最短距离来逐步
50 0
|
1月前
排序和查找
排序和查找
38 0
|
搜索推荐
查找-之二叉排序树(查找、插入、删除)
有序的线性表采用:折半/二分、插值、斐波那契查找相比顺序查找效率得到提高,但是在插入和删除时效率低(为维持数据的有序性) 在高效实现查找操作时,如何提高插入和删除的效率? 在一些应用场景:在查找时需要插入和删除
95 0
查找-之二叉排序树(查找、插入、删除)
|
存储 算法
查找-之有序表查找
待查找的表是有序排列的
69 0
查找-之有序表查找
|
算法 大数据 索引
算法查找——分块查找
分块查找是折半查找(二分查找)和顺序查找的一种改进方法,分块查找由于只要求索引表是有序的,对块内节点没有排序要求,因此特别适合于节点动态变化的情况。分块查找的速度虽然不如折半查找算法,但比顺序查找算法快得多,同时又不需要对全部节点进行排序
193 0
算法查找——分块查找
|
开发者 Python
查找相关的方法 | 学习笔记
快速学习查找相关的方法
55 0
|
存储 算法 Java
练习2—数据查找
练习2—数据查找
|
测试技术
简单查找
给定一个集合,查找元素是否在集合中出现。
112 0