线性结构检索:从数组和链表的原理初窥检索本质

简介: 本课探讨数组与链表的检索原理。数组连续存储,支持随机访问,适合二分查找,效率为O(log n);链表非连续存储,检索需遍历,效率为O(n),但插入删除更高效。通过理解二者存储特性对检索的影响,掌握“合理组织数据以缩小查询范围”的核心思想,并学会改造链表等结构优化性能,为深入学习高级检索技术打下基础。(238字)

今天我们主要探讨的是,对于数组和链表这样的线性结构,我们是怎么检索的。希望通过这个探讨的过程,你能深入理解检索到底是什么。

你可以先思考一个问题:什么是检索?从字面上来理解,检索其实就是将我们所需要的信息,从存储数据的地方高效取出的一种技术。所以,检索效率和数据存储的方式是紧密联系的。具体来说,就是不同的存储方式,会导致不同的检索效率。那么,研究数据结构的存储特点对检索效率的影响就很有必要了。

那今天,我们就从数组和链表的存储特点入手,先来看一看它们是如何进行检索的。

数组和链表有哪些存储特点?

数组的特点相信你已经很熟悉了,就是用一块连续的内存空间来存储数据。那如果我申请不到连续的内存空间怎么办?这时候链表就可以派上用场了。链表可以申请不连续的空间,通过一个指针按顺序将这些空间串起来,形成一条链,链表 也正是因此得名。不过,严格意义上来说,这个叫 单链表。如果没有特别说明,下面我所提到的链表,指的都是只有一个后续指针的单链表。

从图片中我们可以看出,数组和链表分别代表了连续空间和不连续空间的最基础的存储方式,它们是线性表(Linear List)的典型代表。其他所有的数据结构,比如栈、队列、二叉树、B+ 树等,都不外乎是这两者的结合和变化。以栈为例,它本质就是一个限制了读写位置的数组,特点是只允许后进先出。

因此,我们只需要从最基础的数组和链表入手,结合实际应用中遇到的问题去思考解决方案,就能逐步地学习和了解更多的数据结构和检索技术。

那么,数组和链表这两种线性的数据结构的检索效率究竟如何呢?我们来具体看一下。

如何使用二分查找提升数组的检索效率?

首先,如果数据是无序存储的话,无论是数组还是链表,想要查找一个指定元素是否存在,在缺乏数据分布信息的情况下,我们只能从头到尾遍历一遍,才能知道其是否存在。这样的检索效率就是 O(n)。当然,如果数据集不大的话,其实直接遍历就可以了。但如果数据集规模较大的话,我们就需要考虑更高效的检索方式。

对于规模较大的数据集,我们往往是先将它通过排序算法转为有序的数据集,然后通过一些检索算法,比如 二分查找算法 来完成高效的检索。

二分查找也叫折半查找,它的思路很直观,就是将有序数组二分为左右两个部分,通过只在半边进行查找来提升检索效率。那二分查找具体是怎么实现的呢?让我们一起来看看具体的实现步骤。

我们首先会从中间的元素查起,这就会有三种查询结果。

第一种,是中间元素的值等于我们要查询的值。也就是,查到了,那直接返回即可。

如果中间元素的值小于我们想查询的值,那接下来该怎么查呢?这就是第二种情况了。数组是有序的,所以我们以中间元素为分隔,左半边的数组元素一定都小于中间元素,也就是小于我们想查询的值。因此,我们想查询的值只可能存在于右半边的数组中。

对于右半边的数组,我们还是可以继续使用二分查找的思路,再从它的中间查起,重复上面的过程。这样不停地「二分」下去,每次的检索空间都能减少一半,整体的平均查询效率就是 O(log n),远远小于遍历整个数组的代价 O(n)。

同理,对于第三种情况,如果中间元素的值大于我们想查询的值,那么我们就只在左边的数组元素查找即可。

由此可见,合理地组织数据的存储可以提高检索效率。检索的核心思路,其实就是通过合理组织数据,尽可能地快速减少查询范围。在专栏后面的章节中,我们会看到更多的检索算法和技术,其实它们的本质都是通过灵活应用各种数据结构的特点来组织数据,从而达到快速减少查询范围的目的。

链表在检索和动态调整上的优缺点

前面我们说了,数据无序存储的话,链表的检索效率很低。那你可能要问了,有序的链表好像也没法儿提高检索效率啊,这是为什么呢?你可以先停下来自己思考一下,然后再看我下面的讲解。

数组的「连续空间存储」带来了可随机访问的特点。在有序数组应用二分查找时,它以 O(1) 的时间代价就可以直接访问到位于中间的数值,然后以中间的数值为分界线,只选择左边或右边继续查找,从而能快速缩小查询范围。

而链表并不具备「随机访问」的特点。当链表想要访问中间的元素时,我们必须从链表头开始,沿着链一步一步遍历过去,才能访问到期望的数值。如果要访问到中间的节点,我们就需要遍历一半的节点,时间代价已经是 O(n/2) 了。从这个方面来看,由于少了「随机访问位置」的特性,链表的检索能力是偏弱的。

但是,任何事情都有两面性,链表的检索能力偏弱,作为弥补,它在动态调整上会更容易。我们可以以 O(1) 的时间代价完成节点的插入和删除,这是「连续空间」的数组所难以做到的。毕竟如果我们要在有序的数组中插入一个元素,为了保证「数组有序」,我们就需要将数组中排在这个元素后面的元素,全部顺序后移一位,这其实是一个 O(n) 的时间代价了。

因此,在一些需要频繁插入删除数据的场合,有序数组不见得是最合适的选择。另一方面,在数据量非常大的场合,我们也很难保证能申请到连续空间来构建有序数组。因此,学会合理高效地使用链表,也是非常重要的。

如何灵活改造链表提升检索效率?

本质上,我们学习链表,就是在学习「非连续存储空间」的组织方案。我们知道,对于非连续空间,可以用指针将它串联成一个整体。只要掌握了这个思想,我们就可以在不同的应用场景中,设计出适用的数据结构,而不需要拘泥于链表自身的结构限制。

我们可以来看一个简单的改造例子。

比如说,如果我们觉得链表一个节点一个节点遍历太慢,那么我们是不是可以对它做一个简单的改造呢?在掌握了链表的核心思想后,我们很容易就能想到一个改进方案,那就是让链表每个节点不再只是存储一个元素,而是存储一个小的数组。这样我们就能大幅减少节点的数量,从而减少依次遍历节点带来的「低寻址效率」。

比如说,我的链表就只有两个节点,每个节点都存储了一个小的有序数组。这样在检索的时候,我可以用二分查找的思想,先查询第一个节点存储的小数组的末尾元素,看看是否是我们要查询的数字。如果不是,我们要么在第一个节点存储的小数组里,继续二分查找;要么在第二个节点存储的小数组里,继续二分查找。这样的结构就能同时兼顾数组和链表的特点了,而且时间代价也是 O(log n)。

可见,尽管常规的链表只能遍历检索,但是只要我们掌握了 非连续存储空间可以灵活调整 的特性,就可以设计更高效的数据结构和检索算法了。

重点回顾

好了,这一讲的内容差不多了,我们一起回顾一下这一讲的主要内容:以数组和链表为代表的线性结构的检索技术和效率分析。

首先,我们学习了具体的检索方法。对于无序数组,我们可以遍历检索。对于有序数组,我们可以用二分查找。链表具有灵活调整能力,适合用在数据频繁修改的场合。

其次,你应该也开始体会到了检索的一些核心思想:合理组织数据,尽可能快速减少查询范围,可以提升检索效率。

今天的内容其实不难,涉及的核心思想看起来也很简单,但是对于我们掌握检索这门技术非常重要,你一定要好好理解。

随着咱们的课程深入,后面我们会一一解锁更多高级的检索技术和复杂系统,但是核心思路都离不开我们今天所学的内容。

因此,从最基础的数组和链表入手,之后结合具体的问题去思考解决方案,这样可以帮助你一步一步建立起你的知识体系,从而更好地掌握检索原理,达到提高代码效率,提高系统设计能力的目的。

课堂讨论

结合今天学习的数组和链表的检索技术和效率分析,你可以思考一下这两个问题。

  1. 对于有序数组的高效检索,我们为什么使用二分查找算法,而不是 3-7 分查找算法,或 4-6 分查找算法?
    二分查找概率更加均匀,没有偏向任何一端,性能波动小。它更加平衡,整体性能稳定,能避免出现最坏情况,否则如果是一直在大的一边查找,那么查找次数就会变多

  2. 对于单个查询值 k,我们已经熟悉了如何使用二分查找。那给出两个查询值 x 和 y 作为查询范围,如果要在有序数组中查找出大于 x 和小于 y 之间的所有元素,我们应该怎么做呢?
    笔者认为:使用两次二分查找找到数组中最小和最大的元素的下标,再按下标取出来即可

相关文章
|
3月前
|
缓存 前端开发 JavaScript
头条面经
涵盖前端、网络、JS核心、框架及算法等多方面知识,包括深拷贝、双向绑定、HTTP缓存、跨域、Vue原理、TCP/UDP、设计模式、事件循环、类型判断、闭包、原型链、性能优化等高频面试题,全面考察技术深度与综合能力。
|
8天前
|
关系型数据库 MySQL Java
分布式事务终极指南:2PC/XA/TCC/SAGA 从底层原理到生产选型全拆解
本文系统解析分布式事务四大主流方案:XA/2PC(强一致但性能差)、TCC(高并发柔性事务)、SAGA(长事务最终一致)及理论基石(ACID/CAP/BASE),涵盖原理、流程、实战代码、优劣对比与生产选型标准,助你深入掌握核心逻辑。
339 3
|
3月前
|
存储 关系型数据库 MySQL
数据库检索:如何使用 B+ 树对海量磁盘数据建立索引?
本讲探讨磁盘环境下大规模数据检索的挑战与解决方案。由于内存有限,数据常存储于磁盘,而磁盘随机访问远慢于内存,因此需减少磁盘IO。B+树通过将索引与数据分离、节点大小对齐磁盘块、分层存储等设计,实现高效磁盘检索。其内部节点仅存索引,叶子节点存数据,并通过链表连接,支持快速查找与范围查询。即使数据在磁盘,也能通过少量IO完成检索,广泛应用于数据库如MySQL。
|
3月前
|
存储 机器学习/深度学习 自然语言处理
倒排索引:如何从海量数据中查询同时带有「极」和「客」的唐诗?
本文通过唐诗检索的场景,对比正排索引与倒排索引的差异。正排索引以文档ID为键,适合根据标题查内容;而倒排索引以关键词为键,记录包含该词的文档列表,显著提升按内容检索的效率。文中详细讲解了倒排索引的构建过程、查询方法(如多关键字交集查询)及链表归并优化,并探讨其在搜索引擎、推荐系统等场景的应用,帮助读者掌握高效检索的核心技术。
|
3月前
|
存储 数据采集 搜索推荐
状态检索:如何快速判断一个用户是否存在?
本文探讨了高效判断对象是否存在的技术方案,对比有序数组、二叉树和哈希表后,重点介绍位图与布隆过滤器。位图利用bit级存储,大幅节省空间;布隆过滤器通过多哈希函数映射,以极小错误率换取更高空间效率,适用于注册去重、爬虫判重等场景,是时间与空间权衡的典范。
|
3月前
|
jenkins Java 持续交付
Jenkins配置编译项目
Jenkins配置编译任务,实现从GitLab拉取代码,通过Maven/Node编译打包,Docker构建镜像并推送至CCE仓库,kubectl更新服务。支持后台app-web、system-server及前端Vue项目自动化构建与部署,结合86/164服务器特性导出镜像tar包,实现全流程持续集成。
|
3月前
|
缓存
浏览器缓存
HTTP缓存依赖Cache-Control和ETag实现。Cache-Control通过public、private、no-cache等指令控制缓存范围与行为,max-age、s-maxage设置缓存时长,must-revalidate控制过期验证;ETag为资源生成唯一标识,客户端通过If-None-Match比对,判断是否返回304,节省带宽,提升性能。
|
3月前
|
JavaScript 前端开发
虚拟滚动技术
插入数万个DOM时,为避免页面卡顿,可采用虚拟滚动技术,仅渲染可视区域内的元素,滚动时动态更新内容,大幅减少DOM数量。相比一次性渲染,性能显著提升,是前端优化的面试加分项。如react-virtualized即为此类解决方案。
|
3月前
|
缓存 网络协议 JavaScript
使用window.performance API
`window.performance` 是W3C提供的性能API,IE9+支持,用于监控页面加载各阶段耗时。包含navigation、timing等字段,可精确统计DNS查询、TCP连接、DOM解析、白屏、domready等关键性能指标,助力前端性能优化。
|
3月前
|
缓存 JavaScript 前端开发
重绘回流过程
浏览器渲染:HTML解析为DOM树,CSS生成CSSOM,合并为渲染树,再布局、绘制。DOM树含所有标签,渲染树仅含可见元素。CSS阻塞渲染但不阻塞DOM解析。回流(布局变动)必触发重绘(外观变动),优化可减少回流重绘,提升性能。