📅  最后修改于: 2020-08-21 10:19:05             🧑  作者: Mango
在本文中,我们将深入探讨Binary Search的思想和Python实现。
二分搜索binary search是一种有效的搜索算法,适用于排序的数组。由于其直观的行为,它通常被用作以对数时间(O(logn))运行的算法的第一个示例,并且是计算机科学中的基本算法。
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是一种自然递归算法,因为在越来越小的数组上重复相同的过程,直到找到大小为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]
,我们以减少end
1或增加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更好,因为这样可以避免对数组进行排序的成本。
如果我们仅对数组进行排序并搜索一次,则对未排序的数组进行线性搜索会更有效。