📜  Python中的二分搜索binary search

📅  最后修改于: 2020-08-21 10:19:05             🧑  作者: Mango

介绍

在本文中,我们将深入探讨Binary Search的思想和Python实现。

二分搜索binary search是一种有效的搜索算法,适用于排序的数组。由于其直观的行为,它通常被用作以对数时间(O(logn))运行的算法的第一个示例,并且是计算机科学中的基本算法。

二分搜索binary search-示例

Binary Search采用分而治之的方法,并依赖于对数组进行排序以消除每次迭代中一半候选对象的事实。更具体地说,它将已排序数组的中间元素与要搜索的元素进行比较,以决定在哪里继续搜索。

如果目标元素大于中间元素-它不能位于集合的前半部分,则将其丢弃。反之亦然。

注意:如果数组中的元素数为偶数,则我们从两个“中间”元素中的哪一个开始都没有关系。

在继续说明二分搜索binary search如何工作之前,让我们快速看一个示例:

如我们所见,我们可以肯定地知道,由于数组已排序,因此x不在原始数组的前半部分。

当我们知道原始数组x的哪一半时,我们可以用那一半重复此精确过程,然后将其再次分成两半,丢弃肯定不包含x的那一半:

我们重复此过程,直到最终得到只包含一个元素的子数组。我们检查该元素是否为x。如果是-我们发现x,如果不是-x根本不在数组中。

如果您仔细研究一下,您会发现在最坏的情况下(x在数组中不存在),我们需要检查的元素数量比未排序的数组要少得多- 线性搜索将需要更多的东西,这是非常低效的。

更准确地说,在最坏的情况下,我们需要检查的元素数是log 2 N,其中N是数组中的元素数。

数组越大,影响越大:

如果数组有10个元素,则只需检查3个元素即可找到x或得出x不存在的结论。占33.3%。

但是,如果我们的数组有10,000,000个元素,则只需检查24个元素。那是0.0002%。

二分搜索binary search实现

二分搜索binary search是一种自然递归算法,因为在越来越小的数组上重复相同的过程,直到找到大小为1的数组为止。但是,当然也有迭代实现,我们将展示两种方法。

递归的

让我们从递归实现开始吧,因为它更自然:

def binary_search_recursive(array, element, start, end):
    if start > end:
        return -1

    mid = (start + end) // 2
    if element == array[mid]:
        return mid

    if element < array[mid]:
        return binary_search_recursive(array, element, start, mid-1)
    else:
        return binary_search_recursive(array, element, mid+1, end)

让我们仔细看一下这段代码。如果start元素高于元素,则退出递归end

if start > end:
        return -1

这是因为只有在数组中不存在该元素时才会发生这种情况。发生的情况是,我们最终在当前子数组中只有一个元素,而该元素与我们正在寻找的元素不匹配。

此时,start等于end。但是,由于element不等于array[mid],我们以减少end1或增加1 的方式再次“拆分”数组start,并且在该条件下存在递归。

我们可以使用其他方法来做到这一点:

if len(array) == 1:
    if element == array[mid]:
        return mid
    else:
        return -1

其余代码执行“检查中间元素,继续在数组的适当一半中搜索”逻辑。我们找到中间元素的索引,并检查我们正在搜索的元素是否匹配它:

mid = (start + end) // 2
if elem == array[mid]:
    return mid

如果不是,则检查元素是否小于或大于中间元素:

if element < array[mid]:
    # Continue the search in the left half
    return binary_search_recursive(array, element, start, mid-1)
else:
    # Continue the search in the right half
    return binary_search_recursive(array, element, mid+1, end)

让我们继续运行此算法,并稍作修改,以便打印出当前正在处理的子数组:

element = 18
array = [1, 2, 5, 7, 13, 15, 16, 18, 24, 28, 29]

print("Searching for {}".format(element))
print("Index of {}: {}".format(element, binary_search_recursive(array, element, 0, len(array))))

运行此代码将导致:

Searching for 18
Subarray in step 0:[1, 2, 5, 7, 13, 15, 16, 18, 24, 28, 29]
Subarray in step 1:[16, 18, 24, 28, 29]
Subarray in step 2:[16, 18]
Subarray in step 3:[18]
Index of 18: 7

可以清楚地看到它在每次迭代中如何将搜索空间减半,并与我们正在寻找的元素越来越近。如果我们尝试搜索数组中不存在的元素,则输出为:

Searching for 20
Subarray in step 0: [4, 14, 16, 17, 19, 21, 24, 28, 30, 35, 36, 38, 39, 40, 41, 43]
Subarray in step 1: [4, 14, 16, 17, 19, 21, 24, 28]
Subarray in step 2: [19, 21, 24, 28]
Subarray in step 3: [19]
Index of 20: -1

只是为了好玩,我们可以尝试搜索一些大型数组,并查看二分搜索binary search找出一个数字是否存在需要花费多少步骤:

Searching for 421, in an array with 200 elements
Search finished in 6 steps. Index of 421: 169

Searching for 1800, in an array with 1500 elements
Search finished in 11 steps. Index of 1800: -1

Searching for 3101, in an array with 3000 elements
Search finished in 8 steps. Index of 3101: 1551

迭代式

迭代方法非常简单,与递归方法相似。在这里,我们只是while循环执行检查:

def binary_search_iterative(array, element):
    mid = 0
    start = 0
    end = len(array)
    step = 0

    while (start <= end):
        print("Subarray in step {}: {}".format(step, str(array[start:end+1])))
        step = step+1
        mid = (start + end) // 2

        if element == array[mid]:
            return mid

        if element < array[mid]:
            end = mid - 1
        else:
            start = mid + 1
    return -1

让我们填充一个数组并在其中搜索一个元素:

array = [1, 2, 5, 7, 13, 15, 16, 18, 24, 28, 29]       

print("Searching for {} in {}".format(element, array))
print("Index of {}: {}".format(element, binary_search_iterative(array, element)))

运行此代码将为我们提供以下输出:

Searching for 18 in [1, 2, 5, 7, 13, 15, 16, 18, 24, 28, 29]
Subarray in step 0: [1, 2, 5, 7, 13, 15, 16, 18, 24, 28, 29]
Subarray in step 1: [16, 18, 24, 28, 29]
Subarray in step 2: [16, 18]
Subarray in step 3: [18]
Index of 18: 7

结论

二分搜索binary search是一种不可思议的算法,可用于大型排序数组,或者当我们计划在单个数组中重复搜索元素时使用。

仅对数组进行一次排序然后使用Binary Search多次查找其中的元素的成本要比对未排序的数组使用Linear Search更好,因为这样可以避免对数组进行排序的成本。

如果我们仅对数组进行排序并搜索一次,则对未排序的数组进行线性搜索会更有效。