用JS实现二分查找

294 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

二分查找是一种效率较高的方法。二分查找要求线性表是有序表,即表中结点按关键字有序,并且要用向量作为表的存储结构。如果利用其对数组中的元素进行查找,则数组中的元素必须按照递增或者递减的规则进行排列。

1.jpeg 上图简单描述了一下二分查找的过程。

1、题目分析

二分查找通过上图能够比较清晰地了解其查找过程,这里不做过多的解释,相信大家都能看明白。一定注意数组中元素是有序的,不要在图书馆丢书的时候使用二分查找哦。

2、解题

这里提供本质上是一样的两种思路,一种使用递归,一种使用循环。最后简单比较一下其性能怎么样。

思路一:循环

1.代码

//非递归、循环
function binarySearch1(arr: number[], target: number): number{
  let low: number = 0;//设置第一个位置
  let high: number = arr.length;//设置第二个位置
  let len = arr.length;
  
  //如果数组长度为0,那么不会存在目标值,直接返回-1
  if(len ==  0) return -1
  for(let i = low; i < high; i++){
    let mid = Math.floor((low + high) / 2);//获得中间
    let midVal = arr[mid]
    if( midVal < target){
      //说明目标值可能在中间值的右侧,那么将变量low改变为中间位置(循环本身会再加1,不会再计算midVal),再继续循环,下次循环会从i=low+1开始
      low = mid;
    }else if(midVal > target){
      //说明目标值可能在中间值的左侧,那么将改变high改变为中间位置,i正常增加
      high = mid;
    }else{
      //说明目标值和中间值相等,直接返回对应的index
      return mid
    }
  }
  //如果循环结束,依然没有找到则直接返回-1
  return -1
}

这里也可以使用while循环,里边逻辑一样,循环条件为low<=high,可能使用while循环更好理解一些,感兴趣的同学可以自行尝试一下,也可以参考下方的递归方式去写一下while循环,还是比较简单的。
2.功能测试

//功能测试
const arrTest = [1,2,3,4,5,6,7,8,9];
const ind = binarySearch1(arrTest, 7)
console.log(ind) //6
const ind1 = binarySearch1(arrTest, 4)
console.log(ind1)//3
const ind2 = binarySearch1(arrTest, 100)
console.log(ind2) //-1
const ind3 = binarySearch1([], 100)
console.log(ind3) //-1
//符合预期

3.复杂度分析

思路二:递归

1.代码

function binarySearch2(arr: number[], target: number, low?: number, high?: number): number{
  const len = arr.length;
  if(len == 0) return -1
  if(low == null) low = 0;
  if(high == null) high = arr.length -1
  
  if(low > high) return -1;//当low大于high的时候说明结束,相等的时候需要继续进行

  const mid = Math.floor((low + high) / 2);//获取中间index
  const midVal = arr[mid];//获取中间值
  if(target < midVal){
    //说明目标值在左侧
    return binarySearch2(arr, target, low, mid - 1)
  }else if(target > midVal){
    //说明目标值在右侧
    return binarySearch2(arr, target, mid + 1, high)
  }else{
    return mid;
  }
}

2.功能测试

const arrTestAgain = [1,2,3,4,5,6,7,8,9];
const ind4 = binarySearch2(arrTestAgain, 7)
console.log(ind4) //6
const ind5 = binarySearch2(arrTestAgain, 4)
console.log(ind5)//3
const ind6 = binarySearch2(arrTestAgain, 100)
console.log(ind6) //-1
const ind7 = binarySearch2([], 100)
console.log(ind7) //-1
//符合预期

3.复杂度分析
两种方案

3、性能比较

const arrTimeTest = [10, 20, 30, 40, 50, 60, 70, 80];
const target = 20;

console.time('binarySearch1')
for(let i = 0; i< 1000 * 10000; i++){
  binarySearch1(arrTimeTest, target)
}
console.timeEnd('binarySearch1') //binarySearch1: 79.932861328125 ms

console.time('binarySearch2')
for(let i = 0; i< 1000 * 10000; i++){
  binarySearch2(arrTimeTest, target)
}
console.timeEnd('binarySearch2')//binarySearch2: 154.64990234375 ms

这里可以简单的看出,二者执行的时间大约是个2倍的关系,虽然有差距,但是二者处于同样的数量级。这里做性能比较,显而易见有点较真的意思,但也正是这样较真,我们可以看出递归的效率相对而言是比较低的。

4、总结

  1. 通过最后的性能比较,我们可以发现使用循环比使用递归要稍微快一些,虽然他们的复杂度是一样的。递归调用了多次函数,执行时间较长,说明函数的调用时是会消耗性能的.
  2. 考察排序,凡有序,必二分;
  3. 凡二分,时间复杂度必包含O(logn),注意包含。
  4. 递归和非递归的对比
    • 递归的代码相对比较简洁
    • 非递归性能会更好一点(和递归时同级别的)