前面我们提到的二分查找适用于有序线性表的查找,此外针对二分查找还有升级版的插值查找,以及利用斐波那契原理进行查找的斐波那契查找,在后面的随笔里会补充对应的实现,一般我们使用二分查找就可以了。
我们也提到二分查找不适用于高速增长的海量数据,因为维护这个排序非常麻烦,所以我们引出索引这种数据结构。
索引是为了加快查找速度而设计的一种数据结构。索引就是把一个关键字与它对应的记录相关联的过程,一个索引由若干索引项构成,每个索引项至少包含关键字和其对应记录在存储器中的位置等信息。
由此可见,索引技术是组织大型数据库和磁盘文件的一种重要技术。
索引技术分为线性索引、树形索引和多级索引,这里我们介绍线性索引。所谓线性索引就是将索引项集合组织为线性结构,也称索引表。以下是一个索引表的示例图:
在线性索引中,我们重点介绍三种:稠密索引、分块索引和倒排索引。
稠密索引
首先是稠密索引。稠密索引是指在线性表中,将数据集中的每个记录对应一个索引项。就像我们上面示例图中的那样。以主键为例,可以将其抽象化如下:
对于稠密索引这个索引表来说,索引项一定按照关键码有序排列,这样可以应用二分查找,以免索引查找本身影响性能。可见,稠密索引性能可以做到和二分查找相当(找到对应关键码就可以通过指针直接指向对应记录),但是索引项长度和数据集一样长,空间复杂度高,如果数据太多需要存放到磁盘上,反复读取磁盘对性能影响很大。
因此,我们又引入了分块索引。
分块索引
为了减少索引项个数,我们对数据集进行分块,并使其分块有序,然后再给每个分块建立一个索引项(索引值是分块中最大关键码),至于分块内部,则不管其有序性,从而减少索引项的个数。在查找的时候在索引项中通过二分查找找到指定索引项,然后根据该索引项中的关键码去相应分块遍历查找指定元素,这是一种折中方案,既兼顾了空间复杂度,又兼顾了时间复杂度。
分块索引图示如下:
这里面有几个概念需要阐述下,首先是分块有序,需要满足两个先决条件:
- 块内无序。即每一块内的记录不要求有序。当然,有序更理想,只不过要花费大量时间和空间的代价。
- 块间有序。即要求后一块的所有关键字都大于前一块的所有关键字。只有块间有序,才能给查找带来效率。
其次,分块索引的索引项包含三个数据项:
- 最大关键码:它存储每一块中的最大关键字。这样做的好处是在它之后的下一块中最小的关键字也能比这一块最大的关键字要大。
- 块长:存储块中的记录个数,以便于循环时使用。
- 块首指针:用于指向块首数据元素的指针,便于开始对这一块的记录开始遍历。
最后,在分块索引中查找,分两块进行:
- 在分块索引表中查找要查找关键字所在的块。由于块间有序,所以可以通过二分查找快速定位(通过不小于给定值的第一个元素,不大于给定值的最后一个元素确定区间,以前面给出的示例图为例,58位于57和96之间,则会去第三块中查找)。
- 根据块首指针找到相应的块,并在块中顺序查找指定值(即关键码,块中无序所以只能顺序查找)。
分块索引的时间复杂度是:O(log(m)+n)
,其中 m 是分块数,n 是块内元素个数,在索引表长度和块内元素相等时,时间复杂度最优。性能要优于顺序查找,但是比二分查找要差。
总体来说,分块索引在兼顾存储空间和查找性能的情况下,被普遍用于数据库查找等技术中。
倒排索引
百度、Google 等搜索引擎为我们日常查找信息带来了巨大的方便,你是否思考过搜索引擎是如何从海量 HTML 文档中通过关键词查找资源的?给大家介绍最简单,也是最基础的搜索引擎技术 —— 倒排索引。
有倒排索引,就有正向索引,正向索引指的是通过文档 ID 找到对应的文档,如果通过文档ID查找对应文档,再在文档中匹配关键词,意味着要扫描所有文档,最后还要排序,对于互联网上的海量资源来说,显然是不可取的。
所以搜索引擎反其道行之,通过分析每个文档,提取其中的关键字,并建立关键词与文档 ID 的映射关系,每个关键词都对应着多个文档 ID。由于不是通过文档来确定属性(这里的属性是关键词),而是通过属性来确定文档,故而将其称作倒排索引。
在涉及中文的文章中,还要进行中文分词。
倒排索引在很多开源搜索工具中,也有应用,比如 Elasticsearch、Lucene 等。
感兴趣的同学可以去研究下诸如 Elasticsearch 倒排索引的实现。