如何用JavaScript编写二进制搜索算法

106 阅读4分钟

二进制搜索算法是一种经典的算法,它可以让我们在O(log n)的时间复杂度内找到一个排序数组中的项目。在这篇文章中,我们将回顾该算法的工作原理,并学习如何在Javascript中实现它。

一个概念性的例子

二进制搜索的工作原理是不断地把一个数组分成两半,然后看中间的数字,直到找到一个匹配的数字(或者没有找到匹配的数字)。假设我们有一个数组[2, 3, 4, 6, 7, 9, 10] ,我们被要求找到数字7 的索引。

image.png

在二进制搜索中,我们首先确定数组中的中间项,并将其与我们要找的数字进行比较。我们可以通过将数组的起始索引(0)与数组的结束索引(6)相加并除以2来找到中间索引。换句话说,中间=(起始+结束)/2=(6+0)/2=3。

image.png

索引3的数字是6,我们将其与我们要找的数字7进行比较。由于6小于7,我们现在知道6左边(包括)的所有项目都小于我们要找的数字(这就是为什么数组排序很关键)。 image.png

由于所有这些数字都被排除了,我们现在可以把它看成是一个新的数组,起始位置是以前中间位置的右边一个,结束位置仍然是数组的末端。

image.png

我们用同样的方法计算中间索引:中间=(开始+结束)/2=(4+6)/2=5。新的中间索引的数字是9,现在大于7,因此我们知道9右边(包括)的所有项目都大于我们要找的数字。

我们重复这个过程,创建一个新的子数组,但这次我们的结束点移到了第五个索引的左边。在这一点上,我们的起点终点索引都是4,这意味着我们的中间索引计算为:中间=(4+4)/2=4。

image.png

现在我们发现,这个数字就是我们要找的那个数字!因此,我们从第5个索引开始返回索引。因此,我们从我们的算法中返回索引4

在JavaScript中实现这个算法

一般来说,一个二进制搜索函数会收到两个输入:排序后的数组和目标数字。通常情况下,函数的目标是输出目标值的索引,或者在没有找到目标值的情况下输出数字-1

function binarySearch(array, target) {
  // TBD
}

看看我们上面的概念回顾,我们可以看到有一个startend 的值在整个算法中移动。因此,我们可以用let 来初始化这些值,假设它们会发生变化。

function binarySearch(array, target) {
  let start = 0;
  let end = array.length - 1;
}

接下来,我们实现一个循环,不断将数组切成两半,直到找到正确的索引。这个循环不可能是无限的,所以我们需要考虑它应该在什么时候终止。我们在概念性的例子中看到,我们可能会出现开始索引等于结束索引的情况,这也是可以的。我们不能有一个开始索引大于结束索引的情况......这就是我们知道我们已经走得太远了。

function binarySearch(array, target) {
  let start = 0;
  let end = array.length - 1;
  while (start <= end) {
    // TBD
  }
  // If we got this far, we never found the target
  return -1;
}

请注意,如果我们到了开始索引大于结束索引的地步,我们已经用尽了数组中的所有项目,目标根本不存在,所以我们返回-1

最后,我们可以实现该算法的核心逻辑。

  • 寻找中间项
  • 如果中间项等于目标项,返回索引(我们找到了!)。
  • 如果中间项小于目标项,将起始索引移到中间项的右边
  • 如果中间项大于目标项,将结束索引移到中间项的左边
function binarySearch(array, target) {
  let start = 0;
  let end = array.length - 1;
  while (start <= end) {
    // Find the middle index
    const middle = Math.floor((start + end) / 2);
    if (array[middle] === target) {
      return middle;
    } else if (array[middle] < target) {
      start = middle + 1;
    } else {
      end = middle - 1;
    }
  }
  // If we got this far, we never found the target
  return -1;
}

你可能已经注意到,我是用Math.floor 来计算中间索引的。这是因为具有偶数项的数组没有真正的中间项,所以我们要确保我们有一个整数索引,而不是像2.5那样的索引。

计算时间复杂度

我在上面提到,这个算法的时间复杂度是O(log n)。这可以通过考虑你可能需要将一个任意长度的列表(n)除以2,直到你只剩下一个项目的次数来计算。所以我们的方程如下,在这里我们要解决的是x

1 = n / (2^x)
2^x = n
log(2^x) = log(n)
x * log(2) = log(n)
x = log(n)

就这样,我们在JavaScript中实现了二进制搜索算法,并得出了其时间复杂度。