二分查找 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\),该方法可以概括如下:

  1. 初始化:\(low \leftarrow 0\), \(high \leftarrow N\)
  2. 如果 \(low \geq high\),查找失败,算法结束
  3. 计算 \(mid \leftarrow \lfloor (low + high)/2 \rfloor\)
  4. 如果 \(L[mid] == key\),返回 \(mid\),算法结束
  5. 如果 \(L[mid] > key\),则\(high \leftarrow mid\),转到第2步
  6. 如果 \(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的主要内容,如果未能解决你的问题,请参考以下文章

二分查找 Binary Search

[01]Binary Search二分查找

[基础算法]二分查找Binary Search

C++ STL中的Binary search(二分查找)

二分查找(Binary Search)Java实现

#14 二分查找(Binary Search)