- 教程
- 文档
【免费下载链接】30-seconds-of-code
Coding articles to level up your development skills
线性搜索(Linear Search)是最基础也最直观的查找算法:通过遍历数组的每一个元素并与目标值逐一比对,找到首个匹配项的索引。本文以 30-seconds-of-code 仓库中的 linear-search.md 为主体,完整讲解该算法的实现原理、O(n)时间复杂度分析、for...in循环与+一元运算符的使用细节,并结合仓库内二分搜索、插入索引等相邻片段,帮助你理解线性搜索在实际应用中的定位与取舍。
算法思想:逐个比对,找到即返回
线性搜索的核心逻辑非常简单:从数组的第一个元素开始,依次与目标元素进行比较,如果相等则立即返回当前索引;如果整个数组遍历完毕仍未命中,则返回-1。
这种"从头到尾逐一检查"的策略不依赖数组的任何排序状态,因此它对无序数组同样适用。这一点与二分搜索形成鲜明对比——后者要求数组必须预先排序,相关实现见仓库中的 binary-search.md。
从数学视角看,线性搜索的最坏情况(元素位于数组末尾或不存在)需要比较全部n个元素,因此其时间复杂度为O(n),n为数组长度。这意味着查找耗时与数组规模成正比:数组越大,线性搜索耗时越长,这是它的主要性能瓶颈。
仓库实现:基于 for...in 的精简版本
30-seconds-of-code 仓库给出的实现极尽精简,完整代码如下:
const linearSearch = (arr, item) => { for (const i in arr) if (arr[i] === item) return +i; return -1; }; linearSearch([2, 9, 9], 9); // 1 linearSearch([2, 9, 9], 7); // -1这段代码有几个值得深挖的实现细节:
1. 用for...in遍历索引而非元素
for...in循环在数组上迭代时产出的是字符串形式的属性键(即索引),因此循环变量i实际上是"0"、"1"、"2"这样的字符串。原文档特意指出,为了把字符串索引还原为真正的数值索引,代码在返回前使用了一元+运算符(即+i)完成隐式类型转换,从而保证linearSearch([2, 9, 9], 9)返回数值1而非字符串"1"。
2. 严格相等比较
条件判断使用===(严格相等),这意味着查找遵循 JavaScript 的严格相等语义,不会触发隐式类型转换。例如linearSearch([1, '1'], 1)会在索引0处命中,而linearSearch([1, '1'], '1')才会在索引1处命中。
3. 未命中返回-1
若循环结束仍未找到匹配项,函数返回-1。这一约定与 JavaScript 内置的Array.prototype.indexOf()保持一致,也是各种查找 API 的事实标准,方便调用方用result === -1判断"元素不存在"。
动手验证:从控制台到测试
你可以在任何支持 ES6 的 Node.js 或浏览器控制台中直接验证该实现。仓库根目录提供了package.json与配套的 eslint.config.js 等工程化配置,若你希望在本地以规范方式运行,可先执行npm install安装依赖,再按项目惯例在代码目录执行 lint 与测试相关脚本。
一个简单的行为验证可以覆盖三种典型场景:命中第一个匹配项、命中靠后的重复项、完全不命中:
const linearSearch = (arr, item) => { for (const i in arr) if (arr[i] === item) return +i; return -1; }; linearSearch([10, 20, 30], 10); // 0,命中首元素 linearSearch([2, 9, 9], 9); // 1,返回第一个匹配项的索引 linearSearch([2, 9, 9], 7); // -1,元素不存在注意第二个用例:数组中存在两个9,线性搜索只返回第一个匹配项的索引(1),这与indexOf()的行为一致。若需要收集全部匹配索引,可以参考仓库中的 array-has-only-one-match-or-many.md 片段——它用Array.prototype.reduce()实现了indexOfAll函数,返回所有匹配索引组成的数组。
性能分析:为什么是 O(n)
线性搜索的性能特征可以从两个维度理解:
- 最好情况:目标元素恰好位于数组首位,一次比较即可返回,时间复杂度
O(1); - 平均与最坏情况:需要遍历约
n/2到n个元素,时间复杂度为O(n)。
由于n与耗时呈线性关系,当数组规模较大时线性搜索会明显变慢。这正是原文档在文末通过@Further reading引导读者继续学习二分搜索的原因。二分搜索在已排序数组上通过反复折半缩小搜索区间,复杂度仅为O(log n),在大数组场景下性能显著优于线性搜索。仓库中对应的完整实现与边界处理见 binary-search.md。
何时该用线性搜索:与内置方法的取舍
原文档特别以[!NOTE]形式强调:当前实现主要用于教学演示,实际项目应优先使用内置的Array.prototype.indexOf()。这一建议背后的理由值得展开:
- 语义一致:
indexOf()返回首个匹配项索引,未命中返回-1,与本文实现的行为完全一致,可以直接替换; - 经过优化的原生实现:内置方法由 JS 引擎以底层代码实现,通常比手写 JavaScript 循环更快;
- 更安全:手写版本依赖
for...in遍历数组,而for...in本是为对象设计的枚举机制,若数组原型链被扩展或存在自定义属性,可能产生意料之外的枚举结果。
因此,实际开发中查找数组中某个值的索引,直接使用arr.indexOf(item)即可。线性搜索的价值主要体现在**算法教学、面试讲解、以及"理解底层原理"**层面——它让你看清一次简单查找背后究竟发生了多少次比较。
进阶:从线性到二分、再到插入位置
理解了线性搜索后,你可以顺着仓库的关联片段继续深入算法进阶路径:
- 二分搜索:针对已排序数组,用
while循环维护左右边界l、r,通过Math.floor((l + r) / 2)计算中间索引并逐步收窄区间,复杂度降至O(log n); - 有序数组的插入索引:利用
Array.prototype.findIndex()/findLastIndex()找到元素应插入的位置,支持升序、降序与比较器函数三种场景; - 二分查找插入位置:在二分搜索基础上仅改动末尾
return语句——不再返回-1,而是返回左边界l,即可在O(log n)时间内解决"搜索插入位置"问题,其实现与 LeetCode 同题约束(O(n log n))相呼应。
这三篇片段与线性搜索共同构成了一条完整的"查找算法"学习链:从最直观的线性遍历开始,理解复杂度差距,再逐步掌握排序数据上的高效策略。
小结
线性搜索是理解查找问题的最佳起点:它用最朴素的"逐个比对"策略解决"数组中找元素索引"这一基础问题,实现只需一个for...in循环与一元+运算符的类型转换技巧。但其O(n)的时间复杂度决定了它在大数据量场景下的局限——正如原文档所建议的,生产代码应使用内置Array.prototype.indexOf(),而需要高性能查找时应转向排序数据上的二分搜索。掌握这一实现与取舍逻辑,是阅读 30-seconds-of-code 中其他算法与数组片段的良好基础。
- 教程
- 文档
【免费下载链接】30-seconds-of-code
Coding articles to level up your development skills
相关推荐
30-seconds-of-code:用 `Array.prototype.reduce()` 查找 JavaScript 数组中的最长元素
30 seconds of code:用 Array.prototype.reduce 查找 JavaScript 数组中的最长元素 本篇文章以 30 seco
教程文档30 Seconds of Code 二分查找实战:在有序 JavaScript 数组中快速定位元素
30 Seconds of Code 二分查找实战:在有序 JavaScript 数组中快速定位元素 二分查找(Binary Search)是计算机科学中最经典
教程文档30-seconds-of-code:用 JavaScript 数组方法查找最高频元素(Most Frequent Array Element)
30 seconds of code:用 JavaScript 数组方法查找最高频元素(Most Frequent Array Element) 本篇指南以 3
教程文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考