跳转到内容

二分搜尋

本页使用了标题或全文手工转换
维基百科,自由的百科全书
(重定向自二分搜索算法
二分搜尋
二分查找过程示意,目标值为7
概况
類別搜索算法
資料結構数组
复杂度
平均時間複雜度
最坏时间复杂度
最优时间复杂度
空間複雜度
最佳解
相关变量的定义
binary search」的各地常用譯名
中国大陸二分查找[1]二分搜索[2]
臺灣二分搜尋[3]

二分查找(英語:binary search[a]是用于查找有序数组英语Sorted array中目标值位置的搜索算法[9][10][11]二分查找比较目标值与数组中间元素的大小,如果两者不相等,则会舍弃不可能包含目标值的那一半区间,然后在剩余区间重复此过程:每次选取新的中间元素并与目标值比较,直至找到目标或区间为空。若区间为空,则说明目标值不存在。

二分查找在最坏情况英语Best, worst and average case下的时间复杂度对数级别,即需做次比较,其中是数组元素的数量。[b][12]除规模较小的数组外,二分查找通常比线性搜索更快。包括哈希表在内的某些数据结构,在搜索效率上可能超过二分查找,但二分查找还可用于查找最接近目标值的上界或下界,即使目标值不在数组中。

二分查找有许多其他形式。例如,分数级联英语Fractional cascading能加快在多个数组中查找同一数值的速度,还能高效地解决计算几何等领域的搜索问题;指数搜索英语Exponential search则将搜索范围扩展至无界列表。二叉搜索树B树等数据结构的实现也基于二分查找原理。

算法

[编辑]

二分查找适用于有序数组英语Sorted array。其首先比较数组中间的元素与目标值:如果目标值与该元素匹配,则返回其在数组中的位置;如果目标值小于该元素,则在数组较小的那一半中继续查找;如果目标值大于该元素,则在数组较大的那一半中继续查找。通过这种方法,每次迭代都能将搜索范围缩小一半。[13]

过程

[编辑]

给定包含个元素的数组,其中的值或记录分别为,且满足。假设目标值为。下面的子程序使用二分查找来寻找在数组中的索引。[13]

  1. 如果,则搜索失败并终止。
  2. (中间元素的位置)为向下取整值,即不大于的最大整数。
  3. 如果,则令,回到步骤2。
  4. 如果,则令,回到步骤2。
  5. 如果,则搜索完成,返回

该过程使用两个变量来跟踪搜索边界。该过程可以用伪代码表示如下,其中变量名和类型与上文相同,floor下取整函数unsuccessful表示搜索失败时的特定返回值:[13]

二分查找过程
function binary_search(A, n, T) is
    L := 0
    R := n − 1
    while L ≤ R do
        m := floor((L + R) / 2)
        if A[m] < T then
            L := m + 1
        else if A[m] > T then
            R := m − 1
        else:
            return m
    return unsuccessful

也可令向上取整值。如此所做,若目标值在数组中出现多次,结果可能会有所不同。

替代过程

[编辑]

上述过程中,每次迭代都会检查中间元素是否等于目标值。而在其他一些实现中,仅剩下一个元素(即)时,才会执行这项检查,这样每次迭代时就无需检查。这种方式的比较循环更快,因为每次迭代少了一次比较,但平均只需要多一次迭代。[14]

赫尔曼·博滕布鲁赫英语Hermann Bottenbruch于1962年首次发表了省略此检查的实现。[14][15]

  1. 时,
    1. (中间元素的位置)为向上取整值,即不小于的最小整数。
    2. 如果,令
    3. 否则说明,令
  2. 现在,搜索完成。如果,返回。否则,搜索失败并终止。

该版本的伪代码如下,其中ceil是上取整函数:

function binary_search_alternative(A, n, T) is
    L := 0
    R := n − 1
    while L != R do
        m := ceil((L + R) / 2)
        if A[m] > T then
            R := m − 1
        else:
            L := m
    if A[L] = T then
        return L
    return unsuccessful

重复元素

[编辑]

若数组中有重复元素,算法会返回任一符合目标值的索引。例如,如果要搜索的数组为,且目标值为,那么算法返回第4个(索引为3)或第5个(索引为4)元素都是正确的。常规过程通常返回第4个元素(索引为3),但并不总是返回第一个重复项(例如数组,这时依然返回第4个元素)。然而,有时需要找到目标值在数组中重复出现的最左侧或最右侧的元素。在上述例子中,第4个元素是值为4的最左侧元素,而第5个元素是值为4的最右侧元素。若对应元素存在,上述的替代过程总是会返回最右侧元素的索引。[15]

查找最左侧元素的过程

[编辑]

要查找最左边的元素,可以使用以下过程:[16]

  1. 时,
    1. (中间元素的位置)为的向下取整值,即不大于的最大整数。
    2. 如果,令
    3. 否则说明,令
  2. 返回

如果,那么是等于的最左侧元素。即使不在数组中,也是在数组中的排名,即数组中小于的元素数量。

该版本的伪代码如下,其中floor是下取整函数:

function binary_search_leftmost(A, n, T):
    L := 0
    R := n
    while L < R:
        m := floor((L + R) / 2)
        if A[m] < T:
            L := m + 1
        else:
            R := m
    return L

查找最右侧元素的过程

[编辑]

要查找最右边的元素,可以使用以下过程:[16]

  1. 时,
    1. (中间元素的位置)为的向下取整值,即不大于的最大整数。
    2. 如果,令
    3. 否则说明,令
  2. 返回

如果,那么是等于的最右侧元素。即使不在数组中,也是数组中大于的元素数量。

该版本的伪代码如下,其中floor是下取整函数:

function binary_search_rightmost(A, n, T):
    L := 0
    R := n
    while L < R:
        m := floor((L + R) / 2)
        if A[m] > T:
            R := m
        else:
            L := m + 1
    return R - 1

近似匹配

[编辑]
二分查找可用于近似匹配。上图中,目标值本身不在数组中,但通过近似匹配,能计算出其排名、前驱、后继、最近邻

二分查找不仅能精确定位目标值,也可方便地扩展到近似匹配,例如可以用来计算给定值的排名(即比它小的元素的数量)、前驱(前一个较小的元素)、后继(下一个较大的元素)、最近邻。而范围查找英语Range query (computer science)(查找两个值之间的元素数量)可利用查询两次排名得到。[17][18]

  • 查询排名可以使用查找最左侧元素的过程来完成。程序的返回值即为小于目标值的元素数量。[17][18]
  • 查询前驱可以通过查询排名来执行。如果目标值的排名为,那么其前驱的位置为[19]
  • 对于后继查询,可以查找最右侧元素。如果得到的结果为,那么目标值的后继位置就是[19]
  • 目标值的最近邻是其前驱或后继之一,取决于哪个位置更接近。
  • 范围查找也很简单。[19]一旦知道了两个值的位置,区间内大于等于第一个值且小于第二个值的元素数量就是两个位置之差。考虑到是否需要将区间的端点包含在内,以及数组中是否包含与端点匹配的元素,其结果可能会相差1。[20][21]

性能

[编辑]
表示二分查找的。这里要查找的数组是,目标值是
最坏情况下,搜索到达树的最深层;而最好情况是目标值恰为中间元素

二分查找的过程可以构建成一棵二叉树,从而得到其比较次数并分析性能。树的根节点是数组的中间元素,左半部分的中间元素是根节点的左子节点,右半部分的中间元素是根节点的右子节点,其余部分以类似方式构建。搜索过程从根节点开始,根据目标值是小于还是大于当前节点的值来选择遍历左子树还是右子树。[12][22]

最坏情况下,二分查找需要比较次,此时搜索会达到树的最深层。对于任何二分查找过程,树的层数总为。若目标元素不在数组中,可能会发生最坏情况:若可以表示为2的某次幂减1,那么查找过程总会遍历到最深层,一定会发生最坏情况;否则,搜索过程可能会在倒数第二层中止,此时比较了次,比最坏情况少一次。[23]

平均情况下,当目标元素在数组中时,二分查找的比较次数是(假设每个元素被搜索的概率相等),近似于;若目标元素不在数组中,二分查找的比较次数平均为(假设范围内及范围外的元素被搜索的概率相等)。[22]

最好情况下,即目标值正好是数组的中间元素,二分查找在一次比较后就能返回其位置。[24]

从迭代次数的角度看,没有任何一种仅通过比较元素大小进行搜索的算法,在平均情况和最坏情况下的性能优于二分查找。表示二分查找的比较树除最底层外,每一层都是完全填满的,因此层数最少。[c]如果不以此方式构造树,搜索算法在每次迭代中只能排除较少的元素,从而增加平均情况及最坏情况下所需的迭代次数。其他基于元素比较的搜索算法便属于这种情况:虽然它们查询某些目标值时可能更快,但若综合考虑所有元素,其平均性能均不及二分查找。二分查找每次将数组一分为二,保证两个子数组的大小尽可能相近。[22]

空间复杂度

[编辑]

二分查找需要使用三个指针(可能为数组索引,或指向内存地址的指针),与数组本身大小无关。因此,在word RAM英语word RAM计算模型中,二分查找的空间复杂度

平均情况的推导

[编辑]

二分查找的平均迭代次数取决于每个元素被搜索到的概率,而成功搜索与失败搜索的平均情况不同。对于成功搜索,则需假设每个元素被搜索的概率相等;对于失败搜索,则需假设数组元素之间及元素之外的每个区间被搜索的概率相等。成功搜索的平均情况是搜索数组中每个元素所需迭代次数之和除以元素数量,失败搜索的平均情况则是搜索数组各区间所需迭代次数之和除以区间数量[22]

成功搜索

[编辑]

在二叉树的表示法中,一次成功搜索可以表示为从树的根节点到目标节点的路径,称为「内部路径」(internal path)。路径长度等于路径中经过的边(节点之间的连接)数目。如果一条路径长度为,则对应搜索所需的迭代次数为(包括初始迭代)。所有内部路径长度之和称作「内部路径长度」(internal path length)。由于从根节点到任何特定节点仅存在一条路径,因此每条内部路径表示对特定元素的一次搜索。如果有个元素(为正整数),内部路径长度记为,则成功搜索的平均迭代次数(其中表示初始迭代)。[22]

由于二分查找是基于元素比较的最优搜索算法,因此问题可简化为求解含个节点的所有可能二叉树中的最小内部路径长度,表达式如下:[25]

例如,对于含7个元素的数组,根节点对应的搜索需1次迭代,下一层的两个节点各需2次,再下一层的四个节点各需3次。因此,此时内部路径长度为:[25]

根据成功搜索平均情况的公式,此时的平均迭代次数为

上述内部路径长度的求和公式可进一步化简为:[22]

将此式代入成功搜索平均迭代次数的表达式中,得到:[22]

为整数时,其与前述成功搜索的平均情况公式完全相同。

失败搜索

[编辑]

失败搜索可在树中增加额外节点以表示,这种结构称为扩展二叉树(extended binary tree)。当树中已有节点(即内部节点)不足两个子节点时,需为之添加额外的子节点(即外部节点),使每个内部节点都有两个子节点。这样一来,失败搜索的过程便可表示为从根节点到外部节点的一条路径,这个外部节点的父节点即为搜索结束时剩下的唯一元素。从根节点到外部节点的路径称为「外部路径」(external path)。所有外部路径的长度之和称作「外部路径长度」(external path length)。若元素个数为正整数,外部路径长度为,则失败搜索的平均迭代次数(其中表示初始迭代)。公式中除以而非的原因是,树中有条外部路径,它们分别表示数组元素之间以及数组边界之外的各个区间。[22]

同样地,这一问题可以简化为确定含个节点的所有二叉树中的最小外部路径长度。对于任意二叉树,外部路径长度与内部路径长度之间满足[25]将先前得到的表达式代入,则有:[22]

再将上式代入平均迭代次数的公式,便可求出失败搜索的平均迭代次数:[22]

另一过程的性能

[编辑]

前文定义的二分查找过程,每次迭代需要做一次或两次比较,其中每次迭代都会检查中间元素是否与目标相等。假设每个元素被搜索到的概率均等,那么平均每次迭代的比较次数为1.5次。算法的一种实现方法是在搜索结束后检查中间元素是否与目标值相等。平均而言,这种方法每次迭代可减少0.5次比较,略微降低了大部分计算机上每次迭代的运行时间。然而,这种方式一定会达到最多的迭代次数,搜索过程平均会额外增加一次迭代。因为即使在最坏情况下,二分查找的比较循环也只执行次,因此除非极大,否则每次迭代效率的微小提升不足以弥补额外增加的迭代次数。[d][26][27]

运行时间和缓存使用

[编辑]

在分析二分查找的性能时,另一个需要考虑的因素是比较两个元素所需的时间。整数字符串的比较时间通常与其编码长度(一般以数表示)呈线性关系。例如,与32位无符号整数相比,两个64位无符号整数的比较时间至多是前者最坏情况的两倍,该情况出现在整数完全相同时。如果元素的编码长度较大(例如大整数类型或长字符串),比较操作的开销会显著增加。此外,比较浮点数实数在计算机中最常用的表示方式)通常也比整数和短字符串耗时更多。[28]

多数计算机架构中,CPU内部配有独立于内存(RAM)的硬件缓存,容量极小但速度极快。因此,考虑到访问局部性,多数CPU会存储最近访问的内存地址及其附近地址的数据。就数组而言,CPU访问某个元素时,会同时缓存该元素以及在RAM中与之相邻的元素,从而更快地顺序访问索引相近的数组元素。然而,二分查找每次跳跃到数组中点,内存跨度往往较大,不像线性搜索哈希表线性探测那样具有良好局部性。因此在较大数组上,实际耗时可能略高于理论预期。[28]

与其他方案的比较

[编辑]

在有序数组中,当插入和删除操作与查找操作交替进行时,二分查找的效率会非常低下。每次插入或删除操作的时间复杂度为。此外,有序数组的内存使用情况可能较为复杂,特别是在需要频繁插入元素的情况下。[29]其他一些数据结构能更高效地支持插入与删除操作。二分查找可以用于精确匹配和集合成员检测英语Set (abstract data type)(即判断目标值是否存在于某个集合内)。虽然一些数据结构能够更快地精确匹配与检测集合成员,但二分查找还可用于高效地执行近似匹配,通常不论值的类型或结构如何,其近似匹配的性能都能达到[30]此外,有序数组上还能高效完成一些操作,例如获取最小值和最大值。[17][18]

线性搜索

[编辑]

线性搜索是简单的搜索算法,其逐个检查记录,直到找到目标值为止。线性搜索可在链表上实现,其插入和删除操作比数组更快。对于有序数组,除非数组很短,否则二分查找通常比线性搜索更快。不过二分查找需要提前对数组排序,[e][32]所有基于元素比较的排序算法(例如快速排序归并排序),最坏情况下都至少需要做次比较。[33]与线性搜索不同,二分查找还能高效地进行近似匹配。此外,在有序数组中,查找最大或最小元素等操作可以高效完成,而无序数组则无法做到。[34][35]

二叉树

[编辑]
二叉搜索树的搜索算法类似于二分查找

二叉搜索树是基于二分查找原理构建的二叉树数据结构。树中元素按序排列,每个元素都可使用类似二分查找的方法执行搜索,其平均时间复杂度为对数级别。二叉搜索树的插入和删除操作平均也为对数时间,通常比有序数组插入和删除的线性时间更快。同时,二叉树也保留了有序数组的所有操作能力,包括范围查询和近似查询。[30][36][37]

不过,二分查找在搜索操作上通常更高效,因为二叉搜索树往往不是完美平衡的,性能会稍逊于二分查找。即使是平衡树(能够自我平衡的二叉搜索树),也很少能达到理论上层数最少的状态。非平衡二叉树甚至可能严重失衡,内部节点(具有两个子节点的节点)数量很少,此时的平均和最坏搜索性能可能接近次比较。[f]此外,二叉搜索树占用的空间也比有序数组更大。[39][40]

二叉搜索树在外部存储设备(如硬盘)的搜索中具有优势,因为可以在文件系统中有效组织。B树推广了这一树结构的组织方式,常用于数据库文件系统等长期存储系统。[41][42]

哈希表

[编辑]

哈希表是将键映射到对应记录的数据结构,一般使用哈希函数实现。对于关联数组,哈希表通常比有序数组上的二分查找更快。[43]大部分哈希表的实现,其平均时间复杂度仅为平摊的常数时间。[g][45]但哈希表只在搜索失败时告知目标不存在,而不能给出邻近值的信息,因此不适合近似匹配,若执行查找下一个较小值、下一个较大值或者最近的键值等操作,效果不佳。[46]二分查找则非常适合近似匹配,且能在对数时间内完成。此外,诸如查找最大或最小元素等操作,在有序数组上可高效完成,而哈希表无法轻易做到。[30]

集合

[编辑]

集合成员检测英语Set (abstract data type)是与搜索类似的问题,任何像二分查找这样的搜索算法都可用于集合成员检测。但也有一些专门用于集合成员检测的算法。例如,当键值范围有限时,位数组是最简单的选择。该结构紧凑地存储一系列,每位代表一个特定范围内的键值。位数组的速度非常快,查询仅需时间。[47]朱迪矩阵中的Judy1类型则能有效处理64位键值。[48]

对于近似结果,布隆过滤器是基于哈希函数的概率型数据结构,其使用位数组和多个哈希函数对键编码,以存储键集合。多数情况下,布隆过滤器比位数组的空间利用率更高,且速度也不会明显变慢:若使用个哈希函数,成员查询仅需时间。不过,布隆过滤器存在误报问题。[h][i][50]

其他数据结构

[编辑]

某些情况下,一些数据结构可能在搜索操作和其他适用于有序数组的操作上比二分查找更高效。对于搜索、近似匹配及有序数组上的一些操作,可以使用专门的数据结构,如van Emde Boas树英语Van Emde Boas tree融合树英语Fusion tree字典树trie)、位数组。这些数据结构通常只有在特定属性的键值(如小整数键值)上更快,否则可能会导致时间或空间效率降低。[30]只要键值能被排序,这些操作在有序数组上仍能保持较高的效率。某些数据结构(如朱迪矩阵)结合了多种方法,不仅缓解了这些问题,还能保持较高效率,同时能够近似匹配。[48]

其他形式

[编辑]

统一二分查找

[编辑]
统一二分查找英语Uniform binary search存储当前中间元素到下一次迭代的中间元素间的索引差值

统一二分查找存储的不是上下界,而是从当前中间元素到下一次迭代的中间元素间的索引差值,会将之事先存入查找表中。例如,要搜索的数组若为[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],当前的中间元素6。此时,左侧子数组[1, 2, 3, 4, 5]的中间元素是3,右侧子数组[7, 8, 9, 10, 11]的中间元素是9。统一二分查找会存储3这个差值(因为从6到左右两个中间元素的索引距离都是3)。[51]为了缩小搜索空间,算法每次将这个差值与当前中间索引相加减。在某些不便于计算中点的系统(如十进制计算机英语Decimal computer)中,这种方法可能更快。[52]

指数搜索

[编辑]
指数搜索英语Exponential search的可视化图,展示了如何确定后续二分查找的上界

指数搜索将二分查找扩展到无界列表。它先会查找首个索引为2的幂且元素大于目标值的位置,然后将该位置作为上界,再切换至二分查找。若目标值的位置为,则在开始二分查找之前,指数搜索最多需要进行次迭代,此后最多再进行次二分查找的迭代。指数搜索也适用于有界列表,但仅当目标值位于数组的开头附近时,才比直接使用二分查找更高效。[53]

插值搜索

[编辑]
插值搜索使用线性插值进行查找的可视化图。在此例中,由于对目标值在数组中位置的估计非常准确,因此无需进一步查找。其他实现可能会使用不同的函数来估计目标位置

插值搜索不是每次计算中点,而是估算目标元素的位置。估算会考虑数组的最小值、最大值以及数组长度。其基本思路是:中点在很多情况下并非最理想的猜测位置。例如,如果目标值接近数组中的最大值,那么目标值很可能靠近数组末尾。[54]

插值函数中最常见的是线性插值。设数组为,下界为,上界为,目标值为,则目标位置的估计值为。若使用线性插值,且数组元素分布均匀或接近均匀,则插值搜索的比较次数为[54][55][56]

对于较小的数组,插值搜索由于有额外的计算开销,其速度通常会比二分查找慢。尽管插值搜索的时间复杂度增长更慢,但只有在数组规模较大时,这种优势才能抵消额外计算所需的开销。[54]

分数级联

[编辑]
分数级联英语Fractional cascading的原理图,展示了每个数组如何拥有指向其他数组中特定元素(例如每隔一个元素)的指针,从而只需执行一次二分查找即可查找所有关联数组

分数级联是用于在多个有序数组中快速搜索同一元素的技术。如果逐个搜索每个数组,时间复杂度为,其中为数组个数。分数级联在每个数组中存储关于元素在其他数组位置的信息,将时间复杂度降至[57][58]

分数级联最初是为了解决计算几何中的多种搜索问题而开发的。后来,它也被应用于数据挖掘互联网协议(IP)路由中。[57]

图的推广形式

[编辑]

二分查找还被推广到某些类型的图结构,其中目标值存储于图的顶点中,而非数组元素内。二叉搜索树即是这种推广的特例。当在树中查询某个节点时,算法要么确定这个节点就是目标,要么知道目标元素所在的子树位置。但推广形式还可以更进一步:给定无向正权图及目标顶点,每次查询一个顶点时,算法要么知道这个顶点就是目标,要么获得从该顶点到目标顶点的最短路径上的某条边信息。实际上,标准二分查找是图为路径时的特例,而二叉搜索树则对应于当查询的顶点不为目标时给出左或右子树边的情况。对于所有无向正权图,均存在算法,最坏情况下也能通过次查询,找到目标顶点。[59]

噪声二分查找

[编辑]
噪声二分查找过程中,比较每对元素都有一定概率出错

噪声二分查找用于处理算法无法可靠地比较数组元素的情况,即比较每对元素大小时,都有一定概率出错。噪声二分查找可在给定的概率下确定目标元素正确的位置,这一概率控制着结果的可靠性。任何噪声二分查找过程期望比较次数至少为,其中二元熵函数英语Binary entropy function表示最终输出错误位置的概率。[60][61][62]噪声二分查找问题也可视作Rényi-Ulam game英语Rényi-Ulam game的特例,[63]即基于20个问题英语Twenty questions的一种版本,其中回答可能会出错。[64]

量子二分查找

[编辑]

经典计算机执行二分查找时,在最坏情况下的迭代次数严格为量子算法执行二分查找的查询次数(对应经典算法的迭代次数)仍然与成正比,但常数因子小于1,因此在量子计算机上具有更低的时间复杂度。任何精确(即总能返回正确结果)的量子二分查找算法,最坏情况下至少需要次查询(其中自然对数)。[65]目前已经发现一种精确的量子二分查找算法,在最坏情况下的查询次数为[66]相比之下,格罗弗算法是用于搜索无序列表的最优量子算法,所需的查询次数为[67]

历史

[编辑]

排序列表元素以提高查找效率,这一思想古人亦有之。目前已知最早的实例是约公元前200年巴比伦的「Inakibit-Anu」泥板,其包含约500个六十进制的数字及其倒数,数字按字典序排列,以便更快地找到特定的元素。此外,爱琴海诸岛上也发现了一些按照姓名首字母排序的人名列表。1286年完成的拉丁语词典《Catholicon英语Catholicon (1286)》,首次给出了完整的字母排序规则,而不仅仅是依照单词前几个字母排序。[15]

1946年,约翰·莫奇利在摩尔学院讲座英语Moore School Lectures(一门计算机科学领域的奠基性课程)中首次提及了二分查找。[15]1957年,威廉·韦斯利·彼得森英语W. Wesley Peterson发表了首个插值搜索算法。[15][68]早期的二分查找算法均只能用于长度为2的幂次减一的数组[j]。直至1960年,德里克·亨利·莱默提出适用于任意长度数组的二分查找算法。[70]1962年,赫尔曼·博滕布鲁赫英语Hermann BottenbruchALGOL 60语言中实现了另一种二分查找版本,将判断相等的比较操作放在末尾,虽使平均迭代次数增加了一次,但每次迭代所需的比较次数减少至一次。[14]统一二分查找则由斯坦福大学的A.K.钱德拉(A. K. Chandra)于1971年开发。[15]1986年,贝尔纳·沙泽勒英语Bernard Chazelle利奥尼达斯·J·吉巴斯引入了「分数级联英语Fractional cascading」概念,用以解决计算几何中的诸多查找问题。[57][71][72]

实现问题

[编辑]

尽管二分查找的基本思想相对简单,但其细节却出奇复杂。

乔恩·本特利在为职业程序员开设的一门课程中布置了二分查找的练习,发现90%的学生在数小时后仍未给出正确解答,主要原因是算法实现有误而无法运行,或是在极少数边界情况英语Edge case下返回错误答案。[73]1988年发表的一项研究显示,二十本教材中只有五本给出了准确的二分查找代码。[74]此外,本特利自身在1986年出版的《编程珠玑》一书中给出的二分查找实现存在溢出错误,这个错误在二十多年里未被发现。Java编程语言库中的二分查找实现也存在相同的溢出问题,且该问题持续了九年多。[75]

在实际编程中,表示索引的变量通常是固定大小的整数。因此在处理非常大的数组时,可能会导致算术溢出。如果使用计算中点,即使的值都在所用数据类型的表示范围内,的值仍可能会超过范围。如果都是非负数,可以通过计算来避免这种情况。[76]

如果循环的退出条件定义不正确,可能会导致无限循环。当超过时,表示搜索失败,必须返回失败的信息。另外,循环应在找到目标元素时退出;若不这么做,那么在循环结束后,必须检查是否成功找到目标元素。本特利发现,大多数在实现二分查找时出错的程序员,都是退出条件出了错。[14][77]

库支持

[编辑]

许多编程语言的标准库包含二分查找例程:

  • C语言在其标准库中提供了bsearch()函数,通常使用二分查找实现,尽管官方标准中并未强制要求。[78]
  • C++标准库中提供了binary_search()lower_bound()upper_bound()equal_range()函数。[79]
  • D语言的标准库Phobos在std.range模块中提供了SortedRange类型(由sort()assumeSorted()函数返回),该类型包含contains()equaleRange()lowerBound()trisect()方法,这些方法默认对提供随机访问的范围使用二分查找技术。[80]
  • COBOL提供了SEARCH ALL动词,用于对COBOL有序表执行二分查找。[81]
  • Gosort标准库包包含SearchSearchIntsSearchFloat64sSearchStrings函数,分别实现了通用的二分查找,以及针对整数、浮点数、字符串切片的特定实现。[82]
  • Java在标准java.util包的ArraysCollections类中提供了一组重载binarySearch()静态方法,用于对Java数组和List(列表)执行二分查找。[83][84]
  • Microsoft.NET Framework 2.0在其集合基类中提供了二分查找算法的静态泛型版本,例如System.ArrayBinarySearch<T>(T[] array, T value)方法。[85]
  • 对于Objective-CCocoa框架Mac OS X 10.6及以上版本中提供了NSArray -indexOfObject:inSortedRange:options:usingComparator:方法;[86]苹果的Core Foundation英语Core Foundation C框架也包含CFArrayBSearchValues()函数。[87]
  • Python提供了模块bisect,在插入元素后仍能保持列表的有序状态,而无需每次插入元素后都对列表排序。[88]
  • Ruby的Array类包含带有内置近似匹配的bsearch方法。[89]
  • Rust的切片原始类型提供了binary_search()binary_search_by()binary_search_by_key()partition_point()方法。[90]

参见

[编辑]

注释和参考文献

[编辑]

注释

[编辑]
  1. ^ 又称折半查找[4][5](英語:half-interval search[6],直译为「半区间搜索」)、对数搜索(英語:logarithmic search[7]),英文中又称binary chop[8]chop有「劈、斩」之意)。
  2. ^ 符号渐近符号表示对数。在此标记下,对数的底数通常无关紧要,因为不同底数的对数之间只相差一个常数因子。具体来说,有,而是常数。
  3. ^ 任何仅通过元素比较进行搜索的算法,都可以用一棵二叉比较树来表示。这棵树中,从根节点出发,到达任意已存在节点的路径称为「内部路径」(internal path)。定义为所有内部路径的长度总和,即「内部路径长度」(internal path length)。如果数组中的每个元素被搜索到的概率相等,那么搜索算法的平均迭代次数为。换言之,该值即为树中所有内部路径长度的平均值再加上根节点处的第一次迭代。之所以如此,是因为内部路径表示搜索算法在寻找目标元素时比较过的元素,而这些路径长度则表示从根节点之后的迭代次数。因此,将这些路径长度的平均值加上根节点处的一次迭代,即得到了平均情况的迭代次数。为让平均比较次数最少,必须使内部路径长度最小。事实上,二分查找构成的树即满足最小内部路径长度的条件。Knuth 1998证明,若树中所有外部节点(即没有子节点的节点)均位于连续的两层内,则该树的外部路径长度(外部路径即指所有从根节点到外部节点的路径)最小。这一结论同样适用于内部路径,因为内部路径长度与外部路径长度存在线性关系为树的节点数)。当每个子树的节点数量近似相同时(也就是每次迭代时都将数组大致分为两半),树中所有外部节点及其直接父节点都将位于连续两层内。因此,二分查找对应的二叉比较树具有最小的内部路径长度,从而使得二分查找的平均比较次数达到最小。[22]
  4. ^ Knuth 1998使用其设计的MIX计算机模型英语MIX (abstract machine)发现,对于成功搜索而言,前述算法(即二分查找在每次迭代末尾检查元素相等性)的平均运行时间为个单位,而标准二分查找的平均运行时间则为个单位。前者的时间复杂度增长稍慢,但初始复杂度更高。[26]
  5. ^ Knuth 1998对这两种搜索算法的运行时间做了形式化分析。在Knuth设计的MIX计算机英语MIX (abstract machine)上,对于成功搜索,二分查找平均耗时为个单位;而在数组末尾加入哨兵节点英语Sentinel node的线性搜索平均耗时为个单位。线性搜索的计算量很少,故初始复杂度较低,但随规模增长,其复杂度很快便会超过二分查找。在MIX计算机上,只有当时,二分查找的性能才会超过带哨兵的线性搜索。[22][31]
  6. ^ 如果在构建二叉搜索树时,按元素的排序顺序插入,或按最小值与最大值交替插入,则会导致生成的二叉搜索树在平均情况和最坏情况下的搜索时间达到最大。[38]
  7. ^ 某些哈希表的实现方式能确保以恒定时间复杂度搜索。[44]
  8. ^ 这是因为,在布隆过滤器中,将哈希函数对应于某个特定键的所有位设置为1,可能会影响其他键的查询,这些键在至少一个哈希函数中与之共享了相同的位。[49]
  9. ^ 一些版本改进了布隆过滤器,优化了其复杂度或支持删除操作。例如,布谷鸟过滤器利用布谷鸟哈希英语Cuckoo hashing技术,实现了这些优势。[49]
  10. ^ 如1、3、7、15、31等。[69]

引用

[编辑]
  1. ^ Sedgewick, Wayne & 谢路云 2012,§3.1.5 有序数组中的二分查找.
  2. ^ 韦纯福. 基于分治思想的二分搜索技术研究. 大众科技. 2008, (3): 58-59. CNKI DZJI200803022需注册账号查阅 (中文(中国大陆)). 
  3. ^ 廖永申; 王欣平. 在嵌入式Linux系統上的新一代網路協定轉換軟體開發. 2003年NCS全國計算機會議. 2006-06-12 [2024-07-16] (中文(臺灣)). 
  4. ^ 骆剑锋. 哈希表与一般查找方法的比较及冲突的解决. 十堰职业技术学院学报. 2007, (5): 96-98. CNKI SYZJ200705034需注册账号查阅 (中文(中国大陆)). 
  5. ^ 王晓东. 一种在TQuery记录集中实现快速搜索的方法. 管理信息系统. 2000, (3): 56-57. CNKI JYXX200003023需注册账号查阅 (中文(中国大陆)). 
  6. ^ Williams, Jr., Louis F. A modification to the half-interval search (binary search) method. Proceedings of the 14th ACM Southeast Conference. ACM: 95–101. 1976-04-22. doi:10.1145/503561.503582可免费查阅 (英语). 
  7. ^ 7.0 7.1 Knuth 1998,§6.2.1 ("Searching an ordered table"), subsection "Binary search".
  8. ^ Butterfield & Ngondi 2016,第46頁.
  9. ^ Cormen et al. 2009,第39頁.
  10. ^ Cormen et al. 2013,第22頁.
  11. ^ 埃里克·韦斯坦因. Binary search. MathWorld. 
  12. ^ 12.0 12.1 Flores, Ivan; Madpis, George. Average binary search length for dense ordered lists. Communications of the ACM. 1971-09-01, 14 (9): 602–603. ISSN 0001-0782. S2CID 43325465. doi:10.1145/362663.362752可免费查阅 (英语). 
  13. ^ 13.0 13.1 13.2 Knuth 1998,§6.2.1 ("Searching an ordered table"), subsection "Algorithm B".
  14. ^ 14.0 14.1 14.2 14.3 Bottenbruch, Hermann. Structure and use of ALGOL 60. Journal of the ACM. 1962-04-01, 9 (2): 161–221. ISSN 0004-5411. S2CID 13406983. doi:10.1145/321119.321120可免费查阅 (英语).  Procedure is described at p. 214 (§43), titled "Program for Binary Search".
  15. ^ 15.0 15.1 15.2 15.3 15.4 15.5 Knuth 1998,§6.2.1 ("Searching an ordered table"), subsection "History and bibliography".
  16. ^ 16.0 16.1 Kasahara & Morishita 2006,第8–9頁.
  17. ^ 17.0 17.1 17.2 Sedgewick & Wayne 2011,§3.1 ("Elementary Symbol Tables"), subsection "Rank and selection".
  18. ^ 18.0 18.1 18.2 Sedgewick, Wayne & 谢路云 2012,§3.1.2.3 排名与选择.
  19. ^ 19.0 19.1 19.2 Goldman & Goldman 2008,第461–463頁.
  20. ^ Sedgewick & Wayne 2011,§3.1 ("Elementary Symbol Tables"), subsection "Range queries".
  21. ^ Sedgewick, Wayne & 谢路云 2012,§3.1.2.4 范围查找.
  22. ^ 22.00 22.01 22.02 22.03 22.04 22.05 22.06 22.07 22.08 22.09 22.10 22.11 Knuth 1998,§6.2.1 ("Searching an ordered table"), subsection "Further analysis of binary search".
  23. ^ Knuth 1998,§6.2.1 ("Searching an ordered table"), "Theorem B".
  24. ^ Chang 2003,第169頁.
  25. ^ 25.0 25.1 25.2 Knuth 1997,§2.3.4.5 ("Path length").
  26. ^ 26.0 26.1 Knuth 1998,§6.2.1 ("Searching an ordered table"), subsection "Exercise 23".
  27. ^ Rolfe, Timothy J. Analytic derivation of comparisons in binary search. ACM SIGNUM Newsletter. 1997, 32 (4): 15–19. S2CID 23752485. doi:10.1145/289251.289255可免费查阅 (英语). 
  28. ^ 28.0 28.1 Khuong, Paul-Virak; Morin, Pat. Array Layouts for Comparison-Based Searching. Journal of Experimental Algorithmics. 2017, 22. Article 1.3. S2CID 23752485. arXiv:1509.05053可免费查阅. doi:10.1145/3053370 (英语). 
  29. ^ Knuth 1997,§2.2.2 ("Sequential Allocation").
  30. ^ 30.0 30.1 30.2 30.3 Beame, Paul; Fich, Faith E. Optimal bounds for the predecessor problem and related problems. Journal of Computer and System Sciences英语Journal of Computer and System Sciences. 2001, 65 (1): 38–72. doi:10.1006/jcss.2002.1822可免费查阅 (英语). 
  31. ^ Knuth 1998,Answers to Exercises (§6.2.1) for "Exercise 5".
  32. ^ Knuth 1998,§6.2.1 ("Searching an ordered table").
  33. ^ Knuth 1998,§5.3.1 ("Minimum-Comparison sorting").
  34. ^ Sedgewick & Wayne 2011,§3.1 ("Elementary Symbol Tables"), subsection "Ordered symbol tables".
  35. ^ Sedgewick, Wayne & 谢路云 2012,§3.1.2 有序符号表.
  36. ^ Sedgewick & Wayne 2011,§3.2 ("Binary Search Trees"), subsection "Order-based methods and deletion".
  37. ^ Sedgewick, Wayne & 谢路云 2012,§3.2.3 有序性相关的方法与删除操作.
  38. ^ Knuth 1998,§6.2.2 ("Binary tree searching"), subsection "But what about the worst case?".
  39. ^ Sedgewick & Wayne 2011,§3.5 ("Applications"), "Which symbol-table implementation should I use?".
  40. ^ Sedgewick, Wayne & 谢路云 2012,§3.5.1 我应该使用符号表的哪种实现.
  41. ^ Knuth 1998,§5.4.9 ("Disks and Drums").
  42. ^ Knuth 1998,§6.2.4 ("Multiway trees").
  43. ^ Knuth 1998,§6.4 ("Hashing").
  44. ^ Knuth 1998,§6.4 ("Hashing"), subsection "History".
  45. ^ Dietzfelbinger, Martin; Karlin, Anna; Mehlhorn, Kurt; Meyer auf der Heide, Friedhelm; Rohnert, Hans; Tarjan, Robert E. Dynamic perfect hashing: upper and lower bounds. SIAM Journal on Computing英语SIAM Journal on Computing. August 1994, 23 (4): 738–761. doi:10.1137/S0097539791194094 (英语). 
  46. ^ Morin, Pat. Hash tables (PDF): 1. [2016-03-28]. (原始内容存档 (PDF)于2022-10-09) (英语). 
  47. ^ Knuth 2011,§7.1.3 ("Bitwise Tricks and Techniques").
  48. ^ 48.0 48.1 Silverstein, Alan, Judy IV shop manual (PDF), Hewlett-Packard: 80–81, (原始内容存档 (PDF)于2022-10-09) (英语) 
  49. ^ 49.0 49.1 Fan, Bin; Andersen, Dave G.; Kaminsky, Michael; Mitzenmacher, Michael D. Cuckoo filter: practically better than Bloom. Proceedings of the 10th ACM International on Conference on Emerging Networking Experiments and Technologies: 75–88. 2014. doi:10.1145/2674005.2674994可免费查阅 (英语). 
  50. ^ Bloom, Burton H. Space/time trade-offs in hash coding with allowable errors. Communications of the ACM. 1970, 13 (7): 422–426. CiteSeerX 10.1.1.641.9096可免费查阅. S2CID 7931252. doi:10.1145/362686.362692 (英语). 
  51. ^ Knuth 1998,§6.2.1 ("Searching an ordered table"), subsection "An important variation".
  52. ^ Knuth 1998,§6.2.1 ("Searching an ordered table"), subsection "Algorithm U".
  53. ^ Moffat & Turpin 2002,第33頁.
  54. ^ 54.0 54.1 54.2 Knuth 1998,§6.2.1 ("Searching an ordered table"), subsection "Interpolation search".
  55. ^ Knuth 1998,§6.2.1 ("Searching an ordered table"), subsection "Exercise 22".
  56. ^ Perl, Yehoshua; Itai, Alon; Avni, Haim. Interpolation search—a log log n search. Communications of the ACM. 1978, 21 (7): 550–553. S2CID 11089655. doi:10.1145/359545.359557可免费查阅 (英语). 
  57. ^ 57.0 57.1 57.2 Chazelle, Bernard; Liu, Ding. Lower bounds for intersection searching and fractional cascading in higher dimension. 33rd ACM Symposium on Theory of Computing英语Symposium on Theory of Computing. ACM: 322–329. 2001-07-06 [2018-06-30]. ISBN 978-1-58113-349-3. doi:10.1145/380752.380818. (原始内容存档于2018-10-29) (英语). 
  58. ^ Chazelle, Bernard; Liu, Ding. Lower bounds for intersection searching and fractional cascading in higher dimension (PDF). Journal of Computer and System Sciences. 2004-03-01, 68 (2): 269–284 [2018-06-30]. CiteSeerX 10.1.1.298.7772可免费查阅. ISSN 0022-0000. doi:10.1016/j.jcss.2003.07.003. (原始内容存档 (PDF)于2022-10-09) (英语). 
  59. ^ Emamjomeh-Zadeh, Ehsan; Kempe, David; Singhal, Vikrant. Deterministic and probabilistic binary search in graphs. 48th ACM Symposium on Theory of Computing英语Symposium on Theory of Computing: 519–532. 2016. arXiv:1503.00805可免费查阅. doi:10.1145/2897518.2897656 (英语). 
  60. ^ Ben-Or, Michael; Hassidim, Avinatan. The Bayesian learner is optimal for noisy binary search (and pretty good for quantum as well) (PDF). 49th Symposium on Foundations of Computer Science英语Annual IEEE Symposium on Foundations of Computer Science: 221–230. 2008. ISBN 978-0-7695-3436-7. doi:10.1109/FOCS.2008.58. (原始内容存档 (PDF)于2022-10-09) (英语). 
  61. ^ Pelc, Andrzej. Searching with known error probability. Theoretical Computer Science. 1989, 63 (2): 185–202. doi:10.1016/0304-3975(89)90077-7可免费查阅 (英语). 
  62. ^ Rivest, Ronald L.; Meyer, Albert R.; Kleitman, Daniel J.; Winklmann, K. Coping with errors in binary search procedures. 10th ACM Symposium on Theory of Computing英语Symposium on Theory of Computing. doi:10.1145/800133.804351可免费查阅 (英语). 
  63. ^ Pelc, Andrzej. Searching games with errors—fifty years of coping with liars. Theoretical Computer Science. 2002, 270 (1–2): 71–109. doi:10.1016/S0304-3975(01)00303-6可免费查阅 (英语). 
  64. ^ Rényi, Alfréd. On a problem in information theory. Magyar Tudományos Akadémia Matematikai Kutató Intézetének Közleményei. 1961, 6: 505–516. MR 0143666 (匈牙利语). 
  65. ^ Høyer, Peter; Neerbek, Jan; Shi, Yaoyun. Quantum complexities of ordered searching, sorting, and element distinctness. Algorithmica英语Algorithmica. 2002, 34 (4): 429–448. S2CID 13717616. arXiv:quant-ph/0102078可免费查阅. doi:10.1007/s00453-002-0976-3 (英语). 
  66. ^ Childs, Andrew M.; Landahl, Andrew J.; Parrilo, Pablo A. Quantum algorithms for the ordered search problem via semidefinite programming. Physical Review A. 2007, 75 (3). 032335. Bibcode:2007PhRvA..75c2335C. S2CID 41539957. arXiv:quant-ph/0608161可免费查阅. doi:10.1103/PhysRevA.75.032335 (英语). 
  67. ^ Grover, Lov K. A fast quantum mechanical algorithm for database search. 28th ACM Symposium on Theory of Computing英语Symposium on Theory of Computing. Philadelphia, PA: 212–219. 1996. arXiv:quant-ph/9605043可免费查阅. doi:10.1145/237814.237866 (英语). 
  68. ^ Peterson, William Wesley. Addressing for random-access storage. IBM Journal of Research and Development. 1957, 1 (2): 130–146. doi:10.1147/rd.12.0130 (英语). 
  69. ^ A000225 a(n) = 2^n - 1. On-Line Encyclopedia of Integer Sequences. [2016-05-07]. (原始内容存档于2016-06-08) (英语). 
  70. ^ Lehmer, Derrick. Teaching combinatorial tricks to a computer. Proceedings of Symposia in Applied Mathematics. 1960, 10: 180–181. ISBN 9780821813102. doi:10.1090/psapm/010可免费查阅 (英语). 
  71. ^ Chazelle, Bernard; Guibas, Leonidas J. Fractional cascading: I. A data structuring technique (PDF). Algorithmica英语Algorithmica. 1986, 1 (1–4): 133–162 [2019-10-06]. CiteSeerX 10.1.1.117.8349可免费查阅. S2CID 12745042. doi:10.1007/BF01840440. (原始内容存档 (PDF)于2016-03-03) (英语). 
  72. ^ Chazelle, Bernard; Guibas, Leonidas J., Fractional cascading: II. Applications (PDF), Algorithmica英语Algorithmica, 1986, 1 (1–4): 163–191 [2019-10-06], S2CID 11232235, doi:10.1007/BF01840441, (原始内容存档 (PDF)于2016-03-04) (英语) 
  73. ^ Bentley 2000,§4.1 ("The Challenge of Binary Search").
  74. ^ Pattis, Richard E. Textbook errors in binary searching. SIGCSE Bulletin. 1988, 20: 190–194. doi:10.1145/52965.53012 (英语). 
  75. ^ Bloch, Joshua. Extra, extra – read all about it: nearly all binary searches and mergesorts are broken. Google Research Blog. 2006-06-02 [2016-04-21]. (原始内容存档于2016-04-01) (英语). 
  76. ^ Ruggieri, Salvatore. On computing the semi-sum of two integers (PDF). Information Processing Letters英语Information Processing Letters. 2003, 87 (2): 67–71 [2016-03-19]. CiteSeerX 10.1.1.13.5631可免费查阅. doi:10.1016/S0020-0190(03)00263-1. (原始内容存档 (PDF)于2006-07-03) (英语). 
  77. ^ Bentley 2000,§4.4 ("Principles").
  78. ^ bsearch – binary search a sorted table. The Open Group Base Specifications 7th. The Open Group. 2013 [2016-03-28]. (原始内容存档于2016-03-21) (英语). 
  79. ^ Stroustrup 2013,第945頁.
  80. ^ std.range - D Programming Language. dlang.org. [2020-04-29]. (原始内容存档于2018-11-27) (英语). 
  81. ^ Unisys, COBOL ANSI-85 programming reference manual 1: 598–601, 2012 
  82. ^ Package sort. The Go Programming Language. [2016-04-28]. (原始内容存档于2016-04-25) (英语). 
  83. ^ java.util.Arrays. Java Platform Standard Edition 8 Documentation. Oracle Corporation. [2016-05-01]. (原始内容存档于2016-04-29) (英语). 
  84. ^ java.util.Collections. Java Platform Standard Edition 8 Documentation. Oracle Corporation. [2016-05-01]. (原始内容存档于2016-04-23) (英语). 
  85. ^ List<T>.BinarySearch method (T). Microsoft Developer Network. [2016-04-10]. (原始内容存档于2016-05-07) (英语). 
  86. ^ NSArray. Mac Developer Library. Apple Inc. [2016-05-01]. (原始内容存档于2016-04-17) (英语). 
  87. ^ CFArray. Mac Developer Library. Apple Inc. [2016-05-01]. (原始内容存档于2016-04-20) (英语). 
  88. ^ 8.6. bisect — Array bisection algorithm. The Python Standard Library. Python Software Foundation. [2018-03-26]. (原始内容存档于2018-03-25) (英语). 
  89. ^ Fitzgerald 2015,第152頁.
  90. ^ Primitive Type slice. The Rust Standard Library. The Rust Foundation. 2024 [2024-05-25]. (原始内容存档于2021-10-05) (英语). 

来源

[编辑]

外部链接

[编辑]