跟着TRAE SOLO学习两大搜索

跟着TRAE SOLO学习两大搜索

1.顺序搜索(Sequential Search)

认识

顺序搜索也称为线性搜索(Linear Search)。它的基本思想是从数据结构的一端开始,逐个检查每个元素,直到找到目标元素或检查完所有元素。

plain 复制代码
function sequentialSearch(arr, target) {
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] === target) {
            return i;
        }
    }
    return -1;
};
const arr = [4, 3, 6, 2, 5, 7, 9, 8, 1]
console.log(sequentialSearch(arr, 8)) // 7

Javascript顺序搜索实现

基础实现

基础实现比较好写,就是一个小标

javascript 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>顺序搜索 Sequential Search</title>
</head>

<body>
    <script>
    // 1 顺序搜索 
    function SequentialSearch(arr, target) {
        for (let i = 0; i < arr.length; i++) {
            if (arr[i] == target) {
                return i;
            }
        }
        return -1;
    }
    // 功能测试
    const arr = [1, 4, 3, 6, 2, 5, 7, 9, 8];
    let target = 2;
    let result = SequentialSearch(arr, target);
    console.log(`元素 ${target} 在数组中的索引是 ${result}`);
    
    </script>
</body>

</html>
哨兵模式(Sentinel Pattern)优化

针对哨兵模式进行优化

也就是将上面循环中的判断条件进行减少为一个

【哨兵模式】将目标值作为哨兵放在了数组的末尾,确保循环一定会终止

javascript 复制代码
 // 需要同时检查两个条件:
// 1. arr[i] === target - 找到目标元素
// 2. i < arr.length - 确保没有超出数组范围

实现

javascript 复制代码
// 2-优化-哨兵模式
  function SequentialSearch(arr, target) {
      // 如果数组为空,直接返回-1
      if (arr.length == 0) {
          return -1
      }
      // 保存最后一个元素
      const last = arr[arr.length - 1];
      arr[arr.length - 1] = target; // 将最后一个元素设为哨兵

      let i = 0;
      while (arr[i] !== target) {
          i++
      }
      // 恢复最后一个元素
      arr[arr.length - 1] = last;
      // 如果找到的位置不是最后一个元素,或者是最后一个元素且值匹配
      if (i < arr.length - 1 || arr[arr.length - 1] === target){
          return i
      }
      return -1;
 }
双向迭代优化

双向迭代顺序搜索,同时从左和右开始进行搜索

javascript 复制代码
/**
 * 3-双向迭代顺序搜索(从前和从后同时搜索)
 * @param {Array} arr - 待搜索的数组
 * @param {*} target - 要查找的目标元素
 * @return {number} 目标元素的索引,如果未找到则返回-1
 */
function iterativeBidirectionalSearch(arr, target) {
    let left = 0;
    let right = arr.length - 1;
    while (left <= right) {
        // 从前向后检查
        if (arr[left] === target) {
            return left;
        }
        // 从后向前检查
        if (arr[right] === target) {
            return right;
        }
        left++;
        right--;
    }
    return -1;
}
概率优化--暂时没理解
javascript 复制代码
  /**
     * 4-基于概率的迭代顺序搜索(高频元素优先)
     * @param {Array} arr - 待搜索的数组
     * @param {*} target - 要查找的目标元素
     * @param {Object} frequencyMap - 可选的频率映射表
     * @return {number} 目标元素的索引,如果未找到则返回-1
     */
    function iterativeProbabilisticSearch(arr, target, frequencyMap = null) {
        // 如果没有提供频率映射表,则创建一个
        if (!frequencyMap) {
            frequencyMap = {};
            for (const item of arr) {
                frequencyMap[item] = (frequencyMap[item] || 0) + 1;
            }
        }
        // 创建一个按频率排序的索引数组
        const indices = Array.from({ length: arr.length }, (_, i) => i);
        indices.sort((a, b) =>
            (frequencyMap[arr[b]] || 0) - (frequencyMap[arr[a]] || 0)
        );

        // 按照排序后的索引顺序搜索
        for (const i of indices) {
            if (arr[i] === target) {
                return i;
            }
        }

        return -1;
    }

    // 使用示例
    const myList = [4, 2, 7, 1, 9, 5, 7, 2, 7];
    console.log(iterativeProbabilisticSearch(myList, 7)); // 输出: 2
缓存优化

下面的优化非常的简单,但是符合V8引擎的设计

每次循环迭代都需要访问arr.length属性调整为 只在循环开始前访问一次arr.length并将其存储在局部变量length中

JavaScript引擎(如V8)对局部变量的访问速度远快于对象属性访问。这是因为:

  1. 局部变量存储在寄存器或栈上,访问速度快
  2. 对象属性存储在堆上,需要通过属性查找机制访问,速度较慢

这种局部变量的访问由于存储在在寄存器或栈上 访问速度明显大于存储在堆上的对象属性,以前我还一直坚持不增加多余局部变量呢

javascript 复制代码
  function iterativeCachedSearch(arr, target) {
      const length = arr.length; // 缓存数组长度
      for (let i = 0; i < length; i++) {
          if (arr[i] === target) {
              return i;
          }
      }
      return -1;
  }

  // 使用示例
  const myList = [4, 2, 7, 1, 9, 5];
  console.log(iterativeCachedSearch(myList, 7)); // 输出: 2

2.二分搜索(Binary Search)

认识

二分搜索,也叫折半搜索,是一种在有序数组中查找特定元素的搜索算法。所以是用二分查找的前提是数组必须是有序的.

二分搜索通过不断将搜索范围减半的方式,快速定位目标元素。

工作原理

  1. 从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束
  2. 如果目标元素大于或小于中间元素,则在数组大于或小于中间元素的那一半中查找
  3. 重复上述过程,直到找到目标元素或搜索范围为空

时间复杂度

  • 最坏情况:O(log n)
  • 平均情况:O(log n)
  • 最好情况:O(1)(目标元素正好在中间)

空间复杂度

  • 迭代实现:O(1)
  • 递归实现:O(log n)(由于递归调用栈)

实现

基础版本

基础版本实现,这里我们先来自己手写一个简单的二分搜索模块

javascript 复制代码
     /**
     * 1-基础迭代二分搜索
     * @param {Array} arr - 已排序的数组
     * @param {*} target - 要查找的目标元素
     * @return {number} 目标元素的索引,如果未找到则返回-1
     */
    function binarySearch(arr,target){
        let left=0;
        let right=arr.length-1;
        while(left <= right){
            const mid=Math.floor((left+ right)/2);
            if(arr[mid]===target){
                return mid;// 找到目标,返回索引
            }else if(arr[mid]< target){
                left = mid +1; // 目标在右半部分
            }else{
                right =mid -1;  // 目标在左半部分
            }
        }
        return -1;
    }

    // 使用示例
    const sortedArray = [1, 3, 5, 7, 9, 11, 13, 15];
    console.log(binarySearch(sortedArray, 7)); // 输出: 3
    console.log(binarySearch(sortedArray, 8)); // 输出: -1
防止整数溢出

接下来我们就跟着trae优化迭代上面版本的二分搜索,有时候我们会出现一种情况

这里我们主要讲的就是left + (right - left) / 2(left + right) / 2 更安全

javascript 复制代码
当 left 和 right 都很大时
和可能会超过 JavaScript 中 Number 类型的最大安全整数(2^53 - 1)
这会导致整数溢出,得到错误的结果

利用下面的写法就可以有效避免我们结果超过Number.MAX_SAFE_INTEGER,也就是通过先计算的方式直接避免最后结果超过最大值

javascript 复制代码
使用 left + (right - left) / 2

// 先计算 (right - left),这个差值很小
// 然后加到 left 上,不会溢出

// left + right 会先计算,结果可能超过 Number.MAX_SAFE_INTEGER
// 导致溢出,得到错误的结果

所以防止整数溢出我们更改为

javascript 复制代码
// 修改前
const mid = Math.floor((left + right) / 2);

// 修改后
const mid = left + Math.floor((right - left) / 2);
添加输入验证
javascript 复制代码
// 添加在函数开始处
// 输入验证
if (!Array.isArray(arr) || arr.length === 0) {
    throw new Error('输入必须是非空数组');
}
提前处理边界情况
javascript 复制代码
// 添加在输入验证之后,循环之前
// 提前处理边界情况
if (arr[0] === target) return 0;
if (arr[arr.length - 1] === target) return arr.length - 1;
优化循环条件
javascript 复制代码
// 修改前
while (left <= right) {

// 修改后
while (left < right) {

// 调整循环体内的逻辑
调整循环体内的逻辑
javascript 复制代码
// 修改前
if (arr[mid] === target) {
    return mid; // 找到目标,返回索引
} else if (arr[mid] < target) {
    left = mid + 1; // 目标在右半部分
} else {
    right = mid - 1; // 目标在左半部分
}

// 修改后
if (arr[mid] < target) {
    left = mid + 1; // 目标在右半部分
} else {
    right = mid; // 目标在左半部分或mid就是目标
}

最后我们优化完成以后

javascript 复制代码
function binarySearch(arr, target) {
  if (!Array.isArray(arr) || arr.length === 0) {
      throw new Error('输入必须是非空数组');
  }
  // 提前处理边界情况
  if (arr[0] === target) return 0;
  if (arr[arr.length - 1] === target) return arr.length - 1;
  let left = 0;
  let right = arr.length - 1;
  // 循环条件优化为 left < right
  while (left < right) {
      // 防止整数溢出的中间值计算
      const mid = left + Math.floor((right - left) / 2);

      if (arr[mid] < target) {
          left = mid + 1; // 目标在右半部分,排除mid
      } else {
          right = mid; // 目标在左半部分或mid就是目标
      }
  }
  // 循环结束后检查left位置是否为目标
  return arr[left] === target ? left : -1;
}
相关推荐
yunyi2 小时前
使用go的elastic库来实现前后端模糊搜索功能
前端·后端
一枚前端小能手2 小时前
2618. 检查是否是类的对象实例(JavaScript)
前端·javascript
ghie90902 小时前
图像去雾算法详解与MATLAB实现
开发语言·算法·matlab
倚肆2 小时前
CSS中transition属性详解
前端·css
云泽8082 小时前
从三路快排到内省排序:探索工业级排序算法的演进
算法·排序算法
快递鸟2 小时前
物流信息总滞后?快递鸟在途监控 API,毫秒级响应让物流透明不等待
前端
fruge3 小时前
前端注释规范:如何写“后人能看懂”的注释(附示例)
前端
小飞大王6663 小时前
JavaScript基础知识总结(四):常见内置构造函数,正则表达式,作用域与闭包
前端·javascript·正则表达式
weixin_468466853 小时前
遗传算法求解TSP旅行商问题python代码实战
python·算法·算法优化·遗传算法·旅行商问题·智能优化·np问题