比二进制搜索有序列表更快

时间:2010-10-30 04:33:53

标签: c++ arrays algorithm search binary-search

是否有一种比二进制搜索更快的算法,用于搜索数组的有序值?

在我的情况下,我在A数组中有一个排序值(可以是任何类型值),如果我看到的值在{{1}的范围内,我需要返回n }}

11 个答案:

答案 0 :(得分:37)

如果值是整数,则可以比O(log n)做得更好,在这种情况下,就n而言,您可以实现的最佳最坏情况运行时间是O(sqrt(log n))。否则,除非输入序列中有模式,否则无法击败O(log n)。在整数的情况下,有两种方法用于击败O(log n)。

首先,您可以使用y-fast树,它通过在哈希表中存储所有前缀来工作,所有前缀都存储至少一个带有该前缀的整数。这使您可以执行二进制搜索以查找最长匹配前缀的长度。这使您能够找到要在其中搜索的元素的后继O(log w),其中w是单词中的位数。有一些细节可以使这项工作只使用线性空间,但它们并不太糟糕(参见下面的链接)。

其次,您可以使用融合树,它使用位技巧使您能够仅在恒定数量的指令中执行w ^ O(1)比较,从而产生O(log n / log w)的运行时间。 / p>

当log w = sqrt(log n)时,这两个数据结构之间的最佳权衡发生,运行时间为O(sqrt(log n))。

有关上述内容的详细信息,请参阅Erik Demaine课程的讲座12和13:http://courses.csail.mit.edu/6.851/spring07/lec.html

答案 1 :(得分:6)

一种可能性就是将其视为找到函数的根源。基本上,找到:

a[i] <= i <= a[i + 1]

相当于:

a[i] - i <= 0 <= a[i + 1] - i

然后你可以尝试像牛顿的方法等等。这些算法在工作时经常比二进制搜索收敛得更快,但我不知道哪一种算法可以保证为所有输入收敛。

http://en.wikipedia.org/wiki/Root-finding_algorithm

答案 2 :(得分:5)

如果列表中的值均匀分布,那么您可以尝试加权拆分而不是二进制拆分,例如如果期望值是从当前下限到当前值的三分之一,那么您可以尝试也是三分之一的元素。这可能会在价值观聚集的名单上遭受重创。

答案 3 :(得分:4)

是和否。是的,平均而言,搜索比二分搜索更快。但我相信它们仍然是O(lg N),只是具有较低的常数。

您希望最大限度地缩短查找元素所需的时间。通常希望使用较少的步骤,并且一种方法是最大化在每个步骤将消除的预期元素数量。通过二分法,总是可以消除一半的元素。如果你对元素的分布有所了解,你可以做得更好。但是,选择分区元素的算法通常比选择中点更复杂,而这种额外的复杂性可能会超过您使用较少步骤所节省的任何时间。

实际上,在这样的问题中,攻击二阶效应(如缓存局部性)比搜索算法更好。例如,在进行重复二进制搜索时,非常频繁地使用相同的几个元素(第一,第二和第三四分位数),因此将它们放在单个缓存行中可能远远优于对列表的随机访问。

将每个级别划分为4个或8个相等的部分(而不是2个)并对其进行线性搜索也可能比二分搜索更快,因为线性搜索不需要计算分区并且数据更少可能导致缓存停顿的依赖项。

但所有这些仍然是O(lg N)。

答案 4 :(得分:3)

下面的算法怎么样? 它被称为指数搜索,是二元搜索的变体之一。 http://en.m.wikipedia.org/wiki/Exponential_search

在大小为n的有序数组A中搜索元素k。 查找A [2 ^ i] for i = 0,1,2,...直到你超越k在A中的位置,然后对数组左边(小于)的部分进行二分搜索。

int exponential_search(int A[], int key)
{
  // lower and upper bound for binary search
  int lower_bound = 0;
  int upper_bound = 1;

  // calculate lower and upper bound
  while (A[upper_bound] < key) {
    lower_bound = upper_bound;
   upper_bound = upper_bound * 2;
  }
  return binary_search(A, key, lower_bound, upper_bound);
}

此算法将在O(log idx)上运行,其中idx是A中的k的索引。(两个stpes都在log idx中)。在最坏的情况下,算法是O(log idx),如果k是A的最大元素之一或者大于A的任何元素。乘法常数大于二进制搜索但算法运行速度更快,非常大数组以及查找朝向数组开头的数据。

我想知道最小尺寸n这个算法比二分搜索更可取,但我不知道。

答案 5 :(得分:1)

您始终可以将它们放在哈希表中,然后搜索将为O(1)。它会占用大量内存,如果你继续添加项目,哈希表可能需要重新分配。重新分组是O(n)但它将分摊到O(1)。这主要取决于你是否能负担得起这个空间以及潜在的缓存未命中。

答案 6 :(得分:1)

首先,衡量然后再进行优化。

你真的需要优化搜索吗?

如果是这样,那么其次,首先考虑算法复杂性。例如。你可以使用树(比如std::map而不是数组)吗?如果是这样,那么它取决于插入/删除与搜索的相对频率,但是手头有一个排序数组的前提表明,与数据集更改相比,搜索频繁,因此有必要做一些额外的工作。插入/删除,使每次搜索更快 - 即对数时间。

如果您发现搜索时间确实是需要解决的瓶颈,并且没有,数据表示无法更改,并且列表很短,那么线性搜索通常会更快,因为每次比较的工作量更少

否则,如果列表较长,并且没有已知或假定的值的特定分布,并且值不能被视为数字,并且内存消耗应该是常量(排除构造哈希表,比如说),然后二进制搜索每次比较产生1位信息,这可能是你为第一次搜索做的最好的。

干杯&amp;第h

答案 7 :(得分:0)

在二进制搜索中,您将列表拆分为两个“子列表”,并且只搜索可能包含该值的子列表。根据数组的大小,如果将数组拆分为两个以上的拼接,则可以看到加速。

您可以通过保留索引来确定首先搜索的数组的哪个区域。就像在一个大城市的电话簿中,你可以从外面看到,你必须开始搜索。 (我无法在文本中表达我的想法,但我找不到更好地解释它的英文链接。)

答案 8 :(得分:0)

如果您要找到大量的数字,并且通过一些侥幸它们也被排序,您可以在O(n + m)中进行,其中m是要查找的数字的数量。基本上只是你的典型合并算法,只需稍加修改就可以记录每个检查过的数字之前插入的值,如果它要插入数组中那么。

你可以随时交换空间...以及其他操作的时间。假设所有元素都是常量p位,你可以创建一个庞大的数组,为你可以查找的每个可能的值存储当前存储的下一个更大值的索引。该数组需要是2 ^ p * lg(n)位,其中n是存储的数值。每次插入或删除都是O(2 ^ p),但通常大约2 ^ p / n,因为你必须更新所有这些索引。

但你的查询现在是O(1)!

好的,好的,这不太实际。但是以类似的方式将输入划分为块可能会减少日志前面的常量。可能。

答案 9 :(得分:0)

虽然在一般情况下你不能做得比O(log N)好,但你至少可以优化它,从而显着减少O(log N)前面的比例常数。

如果必须在同一个阵列上执行多次搜索,可以使用SIMD扩展对这些进行矢量化,从而进一步降低计算成本。

特别是,如果您正在处理满足某些属性的浮点数数组,那么有多种方法可以构造一个特殊的索引,然后允许在O(1)中搜索数组。

所有上述方面都与测试结果进行了讨论: Cannizzo, 2015, Fast and Vectorizable Alternative to Binary Search in O(1) Applicable to a Wide Domain of Sorted Arrays of Floating Point Numbers 该论文附带github的源代码。

答案 10 :(得分:0)

它在杂项注释中被提及,但是我认为对这个特定问题(“任意类型,数组中的值”)的自然而简单的回答将是插值搜索:

插值搜索会考虑数组中的最低和最高元素以及数组的长度,而不是计算中点,而是估算目标值的位置。它的工作原理是,在许多情况下中点不是最佳猜测。例如,如果目标值接近数组中的最高元素,则很可能位于数组末尾附近。

引用来自:https://en.wikipedia.org/wiki/Binary_search_algorithm

主页:https://en.wikipedia.org/wiki/Interpolation_search

在均匀分布的假设下它可以接近O(log log N)

由于如今与内存访问相比,CPU是如此之快(RAM程序就像您曾经对磁盘所做的那样),因此与每次获取数据相比,索引/比较计算可能会便宜。一旦搜索范围变得足够狭窄(利用内存/缓存局部性),使用线性搜索还可以提高性能。