二分查找 Binary Search
Posted data_algorithms
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分查找 Binary Search相关的知识,希望对你有一定的参考价值。
二分查找 Binary Search
二分查找算法
二分查找(Binary Search)法,是在一个有序的集合中查找指定键值的一种方法。假设有一个集合,集合中的元素按照键值从小到大有序。集合中元素的键可以用一个列表 \(list = (x_1, x_2, ..., x_n)\)表示,其中\(x_i \leq x_i+1\)。任务是要查找一个键\(key\)在这个列表中的位置。
例如,\(list=(-5, 4, 8, 9, 19, 81, 195, 803)\),要查找 \(9\)是列表中的第几个数。
如果\(key\)在列表中,则报告\(key\)的位置,否则报告查找失败。如果\(key\)在列表中多次出现(必定相邻),则返回其中任意一次出现的位置。
二分查找法也叫折半查找法,该方法首先比较\(key\)和列表正中间位置\(mid\)对应元素的键,若\(key\)与\(mid\)位置的元素相等,则返回\(mid\)。若\(key\)大于\(mid\)位置的元素,则在列表的右半段子列表中继续查找,否则在列表的左半段中继续查找,直到子列表的长度为0。
假设列表用\(L\)表示,其长度为\(N\),该方法可以概括如下:
- 初始化:\(low \leftarrow 0\), \(high \leftarrow N\)
- 如果 \(low \geq high\),查找失败,算法结束
- 计算 \(mid \leftarrow \lfloor (low + high)/2 \rfloor\)
- 如果 \(L[mid] == key\),返回 \(mid\),算法结束
- 如果 \(L[mid] > key\),则\(high \leftarrow mid\),转到第2步
- 如果 \(L[mid] < key\), 则\(low \rightarrow mid + 1\),转到第2步
python 实现
二分查找法采用的是分治策略,不算地缩小解的搜索空间,可以用递归的方式实现,也可以用循环的方式实现。
如果采用递归的方式实现,递归的基线条件是待搜索的序列长度为0。每一次递归中,比较序列中间元素与键的差别,根据比较结果,相等则返回,否则在递归地在子序列中继续搜索。
def binary_search_r(lst, key, low=0, high=None):
"""
在有序列表lst中查找key,递归实现
@lst: 有序列表
@key: 查找的键
"""
if high is None:
high = len(lst)
if low >= high: # 基线条件
return None
mid = (low + high) // 2
if key == lst[mid]: # 查找成功
return mid
elif key < lst[mid]:
high = mid # 左子序列递归查找
return binary_search_r(lst, key, low, high)
else:
low = mid + 1 # 右子序列查找
return binary_search_r(lst, key, low, high)
如果采用循环实现,则在循环中记录当前查找的\(low\),\(high\)两个位置,根据序列中间元素与键的大小比较结果,要么查找成功,要么改变\(low\)或\(high\)的值,直到\(low \geq high\)。
def binary_search(lst, key):
"""
在有序列表lst中查找key,循环实现
@lst: 有序列表
@key: 查找的键
"""
low = 0
high = len(lst)
while low < high:
mid = (low + high) // 2
if key == lst[mid]:
return mid
elif key < lst[mid]:
high = mid
else:
low = mid + 1
return None
下面是简单的测试代码:
if __name__ == "__main__":
x = [3, 5, 6, 7, 9, 100, 107]
for ax in x:
print(ax, binary_search_r(x, ax), binary_search(x, ax))
for ax in [-5, -1, 0, 10, 105, 123]:
print(ax, binary_search_r(x, ax), binary_search(x, ax))
二分查找算法分析
正确性
二分查找的每一次迭代中,都将查找范围限定为 \(low\)和\(high\)之间,也就是认为\(key\)如果存在在\(L\)中,则必定有\(L[low] \leq key < L[high]\)。所以要算法正确,只要这个命题成立: 若\(key \in L\),那么在每一次迭代中,\(L[low] \leq key < L[high]\)必然成立。- 初始化时,\(low = 0, high=N\),命题成立。
- 假设在第\(k\)步命题成立,那么 \(L[low_k] \leq key < L[high_k]\)。
在第\(k+1\)步,\(mid = \lfloor (low_k + high_k) / 2 \rfloor\):
- 若\(key > L[mid]\) ,则\(key\)满足$L[mid] < key < L[high_k] $ 。新子区间取\(I = [L[mid], L[high_k])\),必有\(key \in I\);
- 若\(key < L[mid]\) ,则\(key\)满足$L[low_k] < key < L[mid] $ 。新子区间子取\(I=[L[low_k], L[mid])\),则\(key \in I\);
- 若\(key = L[mid]\) ,则查找成功。
时间复杂度
最差情况下,键不在列表中,但是二分查找直到最后子序列长度为0才停止。假设原序列长度为\(N\),每轮迭代将范围缩小一半。第一轮迭代后,待查序列长度为\(N/2\),第二轮迭代后,待查序列长度为 \(N/2^2\)。设最大迭代次数为\(k\),则\(N/2^k-1 >1, N/2^k \leq 1\),即\(k = \lceil \log_2 N \rceil\)。因此,对于长度为\(N\)的表,二分查找的最大迭代次数时\(\log_2 N\),算法复杂度为 \(O(\log_2N)\)。
以上是关于二分查找 Binary Search的主要内容,如果未能解决你的问题,请参考以下文章