8.5 线性索引查找
我们前面讲的几种比较高效的查找方法都是基于有序的基础之上的,但事实上,很多数据集可能增长非常快,例如,某些微博网站或大型论坛的帖子和回复总数每天都是成百万上千万条,如图8-5-1所示,或者一些服务器的日志信息记录也可能是海量数据,要保证记录全部是按照当中的某个关键字有序,其时间代价是非常高昂的,所以这种数据通常都是按先后顺序存储。
图8-5-1
那么对于这样的查找表,我们如何能够快速查找到需要的数据呢?办法就是——索引。
数据结构的最终目的是提高数据的处理速度,索引是为了加快查找速度而设计的一种数据结构。索引就是把一个关键字与它对应的记录相关联的过程,一个索引由若干个索引项构成,每个索引项至少应包含关键字和其对应的记录在存储器中的位置等信息。索引技术是组织大型数据库以及磁盘文件的一种重要技术。
索引按照结构可以分为线性索引、树形索引和多级索引。我们这里就只介绍线性索引技术。所谓线性索引就是将索引项集合组织为线性结构,也称为索引表。我们重点介绍三种线性索引:稠密索引、分块索引和倒排索引。
8.5.1 稠密索引
我母亲年纪大了,记忆力不好,经常在家里找不到东西,于是她想到了一个办法。她用一小本子记录了家里所有小东西放置的位置,比如户口本放在右手床头柜下面抽屉中,针线放在电视柜中间的抽屉中,钞票放在衣柜……咳,这个就不提了(同学们坏笑)。总之,她老人家把这些小物品的放置位置都记录在了小本子上,并且每隔一段时间还按照本子整理一遍家中的物品,用完都放回原处,这样她就几乎再没有找不到东西。
记得有一次我申请职称时,单位一定要我的大学毕业证,我在家里找了很长时间未果,急得要死。和老妈一说,她的神奇小本子马上发挥作用,一下子就找到了,原来被她整理后放到了衣橱里的抽屉里。
从这件事就可以看出,家中的物品尽管是无序的,但是如果有一个小本子记录,寻找起来也是非常容易,而这小本子就是索引。
稠密索引是指在线性索引中,将数据集中的每个记录对应一个索引项,如图8-5-2所示。
图8-5-2
刚才的小例子和稠密索引还是略有不同,家里的东西毕竟少,小本子再多也就几十页,全部翻看完就几分钟时间,而稠密索引要应对的可能是成千上万的数据,因此对于稠密索引这个索引表来说,索引项一定是按照关键码有序的排列。
索引项有序也就意味着,我们要查找关键字时,可以用到折半、插值、斐波那契等有序查找算法,大大提高了效率。比如图8-5-2中,我要查找关键字是18的记录,如果直接从右侧的数据表中查找,那只能顺序查找,需要查找6次才可以查到结果。而如果是从左侧的索引表中查找,只需两次折半查找就可以得到18对应的指针,最终查找到结果。
这显然是稠密索引优点,但是如果数据集非常大,比如上亿,那也就意味着索引也得同样的数据集长度规模,对于内存有限的计算机来说,可能就需要反复去访问磁盘,查找性能反而大大下降了。
8.5.2 分块索引
回想一下图书馆是如何藏书的。显然它不会是顺序摆放后,给我们一个稠密索引表去查,然后再找到书给你。图书馆的图书分类摆放是一门非常完整的科学体系,而它最重要的一个特点就是分块。
图8-5-3
稠密索引因为索引项与数据集的记录个数相同,所以空间代价很大。为了减少索引项的个数,我们可以对数据集进行分块,使其分块有序,然后再对每一块建立一个索引项,从而减少索引项的个数。
分块有序,是把数据集的记录分成了若干块,并且这些块需要满足两个条件:
- 块内无序,即每一块内的记录不要求有序。当然,你如果能够让块内有序对查找来说更理想,不过这就要付出大量时间和空间的代价,因此通常我们不要求块内有序。
- 块间有序,例如,要求第二块所有记录的关键字均要大于第一块中所有记录的关键字,第三块的所有记录的关键字均要大于第二块的所有记录关键字……因为只有块间有序,才有可能在查找时带来效率。
对于分块有序的数据集,将每块对应一个索引项,这种索引方法叫做分块索引。如图8-5-4所示,我们定义的分块索引的索引项结构分三个数据项:
- 最大关键码,它存储每一块中的最大关键字,这样的好处就是可以使得在它之后的下一块中的最小关键字也能比这一块最大的关键字要大;
- 存储了块中的记录个数,以便于循环时使用;
- 用于指向块首数据元素的指针,便于开始对这一块中记录进行遍历。
图8-5-4
在分块索引表中查找,就是分两步进行:
1.在分块索引表中查找要查关键字所在的块。由于分块索引表是块间有序的,因此很容易利用折半、插值等算法得到结果。例如,在图8-5-4的数据集中查找62,我们可以很快可以从左上角的索引表中由57<62<96得到62在第三个块中。
2.根据块首指针找到相应的块,并在块中顺序查找关键码。因为块中可以是无序的,因此只能顺序查找。
应该说,分块索引的思想是很容易理解的,我们通常在整理书架时,都会考虑不同的层板放置不同类别的图书。例如,我家里就是最上层放不太常翻阅的小说书,中间层放经常用到的如菜谱、字典等生活和工具用书,最下层放大开本比较重的计算机书。这就是分块的概念,并且让它们块间有序了。至于上层中《红楼梦》是应该放在《三国演义》的左边还是右边,并不是很重要。毕竟要找小说《三国演义》,只需要对这一层的图书用眼睛扫过一遍就能很容易查找到。
我们再来分析一下分块索引的平均查找长度。设n个记录的数据集被平均分成m块,每个块中有t条记录,显然n=m×t,或者说m=n/t。再假设Lb为查找索引表的平均查找长度,因最好与最差的等概率原则,所以Lb的平均长度为(m+1)/2。Lw为块中查找记录的平均查找长度,同理可知它的平均查找长度为(t+1)/2。
这样分块索引查找的平均查找长度为:
注意上面这个式子的推导是为了让整个分块索引查找长度依赖n和t两个变量。从这里了我们也就得到,平均长度不仅仅取决于数据集的总记录数n,还和每一个块的记录个数t相关。最佳的情况就是分的块数m与块中的记录数t相同,此时意味着n=m×t=t2,即ASLw=1/2·(n/t+t)+1=t+1=sqrt(n)+1。
可见,分块索引的效率比之顺序查找的O(n)是高了不少,不过显然它与折半查找的O(logn)相比还有不小的差距。因此在确定所在块的过程中,由于块间有序,所以可以应用折半、插值等手段来提高效率。
总的来说,分块索引在兼顾了对细分块不需要有序的情况下,大大增加了整体查找的速度,所以普遍被用于数据库表查找等技术的应用当中。
8.5.3 倒排索引
我不知道大家有没有对搜索引擎好奇过,无论你查找什么样的信息,它都可以在极短的时间内给你一些结果,如图8-5-5所示。是什么算法技术达到这样的高效查找呢?
图8-5-5
我们在这里介绍最简单的,也算是最基础的搜索技术——倒排索引。
我们来看样例,现在有两篇极短的英文“文章”——其实只能算是句子,我们暂认为它是文章,编号分别是1和2。
1.Books and friends should be few but good.(读书如交友,应求少而精。)
2.A good book is a good friend.(好书如挚友。)
假设我们忽略掉如“books”、“friends”中的复数“s”以及如“A”这样的大小写差异。我们可以整理出这样一张单词表,如表8-5-1所示,并将单词做了排序,也就是表格显示了每个不同的单词分别出现在哪篇文章中,比如“good”它在两篇文章中都有出现,而“is”只是在文章2中才有。
表8-5-1
英文单词 文章编号 a 2 and 1 be 1 book 1,2 but 1 few 1 friend 1,2 good 1,2 is 2 should 1
有了这样一张单词表,我们要搜索文章,就非常方便了。如果你在搜索框中填写“book”关键字。系统就先在这张单词表中有序查找“book”,找到后将它对应的文章编号1和2的文章地址(通常在搜索引擎中就是网页的标题和链接)返回,并告诉你,查找到两条记录,用时0.0001秒。由于单词表是有序的,查找效率很高,返回的又只是文章的编号,所以整体速度都非常快。
如果没有这张单词表,为了能证实所有的文章中有还是没有关键字“book”,则需要对每一篇文章每一个单词顺序查找。在文章数是海量的情况下,这样的做法只存在理论上可行性,现实中是没有人愿意使用的。
在这里这张单词表就是索引表,索引项的通用结构是:
- 次关键码,例如上面的“英文单词”;
- 记录号表,例如上面的“文章编号”。
其中记录号表存储具有相同次关键字的所有记录的记录号(可以是指向记录的指针或者是该记录的主关键字)。这样的索引方法就是倒排索引(in-verted index)。倒排索引源于实际应用中需要根据属性(或字段、次关键码)的值来查找记录。这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引。
倒排索引的优点显然就是查找记录非常快,基本等于生成索引表后,查找时都不用去读取记录,就可以得到结果。但它的缺点是这个记录号不定长,比如上例有7个单词的文章编号只有一个,而“book”、“friend”、“good”有两个文章编号,若是对多篇文章所有单词建立倒排索引,那每个单词都将对应相当多的文章编号,维护比较困难,插入和删除操作都需要作相应的处理。
当然,现实中的搜索技术非常复杂,比如我们不仅要知道某篇文章有要搜索的关键字,还想知道这个关键字在文章中的哪些地方出现,这就需要我们对记录号表做一些改良。再比如,文章编号上亿,如果都用长数字也没必要,可以进行压缩,比如三篇文章的编号是“112,115,119”,我们可以记录成“112,+3,+4”,即只记录差值,这样每个关键字就只占用一两个字节。甚至关键字也可以压缩,比如前一条记录的关键字是“and”而后一条是“an-droid”,那么后面这个可以改成“<3,roid>”,这样也可以起到压缩数据的作用。再比如搜索时,尽管告诉你有几千几万条查找到的记录,但其实真正显示给你看的,就只是当中的前10或者20条左右数据,只有在点击下一页时才会获得后面的部分索引记录,这也可以大大提高了整体搜索的效率。
呵呵,有同学说得没错,如果文章是中文就更加复杂。比如文章中出现“中国人”,它本身是关键字,那么“中国”、“国人”也都可能是要查找的关键字——啊,太复杂了,你还是自己去找相关资料吧。如果想彻底明白,努力进入google或者百度公司做搜索引擎的软件工程师,我想他们会满足你对技术知识的渴求。
我们课堂上就是起到抛砖引玉的作用,希望可以让你对搜索技术产生兴趣,我会非常欣慰的,休息一下。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论