JavaScript中的二进制搜索算法

294 阅读5分钟

在这篇文章中,我将比较线性搜索和二进制搜索算法。你将学习线性和二进制算法的伪代码,看到演示这两种方法的例子,了解时间复杂度,并获得关于如何实现算法的分步指南。

简介

作为一名程序员,你希望找到解决问题的最佳方案,这样你的代码不仅正确而且高效。选择一个次优的算法可能意味着更长的完成时间,增加代码的复杂性,或者更糟糕的是,一个程序会崩溃。

你可能使用过一种搜索算法来定位数据集合中的项目。JavaScript语言有几种方法,比如find ,来定位数组中的项目。然而,这些方法都是使用线性搜索。线性搜索算法从一个列表的开头开始,将每个元素与搜索值进行比较,直到找到为止。

当你有少量元素的时候,这很好。但当你搜索有数千或数百万元素的大列表时,你需要一个更好的方法来定位项目。这时你就需要使用二进制搜索。

在本教程中,我将解释二进制搜索如何工作,以及如何在JavaScript中实现该算法。首先,我们将回顾一下线性搜索算法。

线性搜索

我们将首先解释如何在JavaScript中实现线性搜索。我们将创建一个名为linearSearch 的函数,它接受一个整数或字符串的值和一个数组作为参数。该函数将在数组中的每个元素中搜索该值,如果找到了,则返回该值在数组中的位置。如果该值不在数组中,它将返回-1

线性搜索的伪代码

Set found to false
Set position to1
Set index to 0
while found is false and index < number of elements
    if list[index] is equal to search value
        Set found to true
        Set position to index
    else Add 1 to index
return position

线性搜索的分步解释

想象一下,我们对线性搜索的输入是[1,9,13,16,3,4,0,12] 。如果我们要搜索的值是16 ,上述逻辑将返回3 。而且,如果我们搜索的是11 ,上述逻辑将返回-1 。让我们把它分解一下。

Linear Search 我们初始化算法,将found 设为falseposition 设为-1index 设为0 。然后我们进行迭代。

步骤indexlist[index]positionfound
101-1false
219-1false
3213-1false
43163true

如果我们按照上述逻辑来处理数组中不存在的元素,你会看到代码返回-1,因为found = false ,而position = -1 ,直到最后。

线性搜索的Javascript实现

这里是线性搜索算法的一个JavaScript实现。

function linearSearch(value, list) {
    let found = false;
    let position = -1;
    let index = 0;
 
    while(!found && index < list.length) {
        if(list[index] == value) {
            found = true;
            position = index;
        } else {
            index += 1;
        }
    }
    return position;
}

线性搜索的属性

值得注意的是,线性搜索算法不需要使用排序的列表。而且,该算法可以被定制,用于不同的场景,比如按键搜索对象的数组。如果你有一个客户数据数组,其中包括名字和姓氏的键,你可以测试该数组是否有一个指定名字的客户。在这种情况下,与其检查list[index] 是否等于我们的搜索值,不如检查list[index].first

线性搜索的时间复杂度

如果被搜索的元素是列表中的第一个元素,那么最佳情况下的时间复杂度就可以实现。现在,搜索将通过一次比较完成。因此,最佳情况下的时间复杂度将是O(1)

如果搜索的元素是最后一个元素或不在列表中,最坏情况下的时间复杂度会发生。在这种情况下,搜索必须比较数组中的所有元素。我们说输入数据的长度为n,这意味着由于进行了n次比较,总体时间复杂度为O(n)

在上面的例子中,我在一个有八个元素的数组上使用了linearSearch 函数。在最坏的情况下,当搜索值不在列表中或在列表的末尾时,该函数将不得不进行8次比较。因为我们的数组非常小,所以没有必要使用不同的算法进行优化。然而,超过一定程度,使用线性搜索算法就不再有效了,这时使用二进制搜索算法会更好。

线性搜索的平均时间复杂度也是O(n)

线性搜索的空间复杂度

这个算法的总体空间复杂度相当于数组的大小。因此,O(n)。你不需要为完成这个算法保留任何额外的空间。

二进制搜索

想象一下,你正在玩一个数字猜测游戏。你被要求在1到100之间猜一个数字。如果你的数字太高或太低,你会得到一个提示。

你的策略会是什么?你会随机地选择数字吗?你会从1开始,然后是2,以此类推,直到你猜对为止?即使你有无限的猜测,你也想在尽可能少的尝试中做出正确的猜测。因此,你可能会从猜测50开始。如果这个数字更高,你可以猜75。如果它更低,那就意味着这个数字在50和75之间,你会选择一个在中间的数字。你会这样继续下去,直到你得出正确的数字。这类似于二进制搜索的工作方式。

有两种实现二进制搜索的方法。

  • 迭代法
  • 递归法

迭代二进制搜索的伪代码

下面是一些表达使用迭代法进行二进制搜索的伪代码。

do until the low and high pointers have not met or crossed
    mid = (low + high)/2
    if (x == arr[mid])
        return mid
    else if (x > arr[mid]) 
        low = mid + 1
    else                     
        high = mid - 1

递归二进制搜索的伪码

这里是使用递归法实现二进制搜索的伪代码。

binarySearch(arr, x, low, high)
    if low > high
        return False 
    else
        mid = (low + high) / 2 
        if x == arr[mid]
            return mid
        else if x > arr[mid]    
            return binarySearch(arr, x, mid + 1, high)
        else                              
            return binarySearch(arr, x, low, mid - 1)

无论使用何种技术,二进制搜索算法总是使用分而治之的方法。

一步一步的解释

让我们考虑一个数组[1,9,13,16,3,5,0,12] ,其中的searchValue13

我们按照执行二进制搜索的要求,从一个排序的数组开始。请注意,对数组进行排序是很昂贵的,但是一旦完成,数组可以被有效地搜索多次。

Sorted Array for Binary Search

现在,高指针和低指针将被分别设置为第一个和最后一个元素。中间指针被设置为(low - high) / 2 所给的索引。

binary search step 1

由于searchValue > mid ,我们需要搜索数组的右侧。所以我们将low 指针设置为刚好在mid 之后,而mid 被重置为在lowhigh 指针之间。

binary search step 2

同样,目标值是在数组的右边。再一次,我们调整低位和高位指针。现在低指针和中指针是一样的。

binary search step 3

现在,searchValue 是在mid ,这意味着我们已经到达了搜索的终点!

步骤lowmidhighlist[mid]
10375
24579
366713

二进制搜索的Javascript实现

现在,让我们用JavaScript来编写二进制搜索算法的代码吧

我们将创建一个函数,binarySearch ,它接受一个值和一个数组作为参数。如果找到,它将返回该值在列表中出现的索引。如果没有找到该值,它将返回-1。这就是我们用JavaScript编写的实现。

//note that list must be sorted for this function to work
function binarySearch(value, list) {
    let low = 0;    //left endpoint
    let high = list.length - 1;   //right endpoint
    let position = -1;
    let found = false;
    let mid;
 
    while (found === false && low <= high) {
        mid = Math.floor((low + high)/2);
        if (list[mid] == value) {
            found = true;
            position = mid;
        } else if (list[mid] > value) {  //if in lower half
            high = mid - 1;
        } else {  //in in upper half
            low = mid + 1;
        }
    }
    return position;
}

时间复杂度

我们使用二进制搜索来寻找数组中的元素的主要原因之一是它的时间复杂性。在最好的情况下,二进制搜索的时间复杂度是O(1)。这发生在数组中间的元素与搜索元素相匹配的时候。

在最坏的情况下,使用二进制搜索搜索一个元素的时间复杂度是O(log n)--对于大的n值,远远小于O(n)。为了了解log(n)的增长速度比n慢多少,这里有一个**log(n)**的典型值的表格。

n对数(n)
11
83
102410
1,000,00019.9
1,000,000,000,000,000,00059.8

所以,你可以看到,n越大,二进制搜索比线性搜索的改进就越大。

平均 情况下的时间复杂度也是O(log n),使用二进制搜索来搜索一个项目。

空间复杂度

实现二进制搜索的空间复杂度也是O(n)

二进制搜索的特性

与线性搜索不同,二进制搜索使用一个排序的列表。这让我们可以使用 "分而治之 "的算法来解决这个问题。

总结

在本教程中,我们看到了如何实现线性搜索和二进制搜索算法。线性搜索算法比较简单,不需要一个排序的数组。然而,在较大的数组中使用它的效率很低。在最坏的情况下,该算法将不得不搜索所有的元素,进行n次比较(其中n是元素的数量)。

另一方面,二进制搜索算法要求你首先对数组进行排序,而且实现起来更加复杂。然而,即使考虑到排序的成本,它也更有效率。例如,一个有10个元素的数组,二进制搜索最多只能进行4次比较,而线性搜索则要进行10次比较--这不是一个很大的改进。然而,对于一个有1,000,000个元素的数组来说,二进制搜索的最坏情况是只有20次比较。这是对线性搜索的一个巨大改进!

知道如何使用二进制搜索并不只是为了面试问题而练习的东西。它是一种实用的技能,可以使你的代码工作得更有效率。