二进制搜索没有统一分布

时间:2013-06-01 12:22:42

标签: performance algorithm binary-search

二进制搜索对于均匀分布非常有效。列表中的每个成员都有相同的“命中”概率。这就是你每次尝试中心的原因。

是否存在无均匀分布的有效算法?例如1 / x分布后的分布。

6 个答案:

答案 0 :(得分:9)

二进制搜索和二叉树之间存在深层联系 - 二叉树基本上是一个“预先计算的”二分搜索,其中切割点由树的结构决定,而不是被选为搜索运行。事实证明,处理每个密钥的概率“权重”有时是用二叉树来完成的。

一个原因是因为它是一个相当普通的二叉搜索树,但事先已知,并且具有查询概率的知识。

Niklaus Wirth在他的“算法和数据结构”一书中介绍了这一点,有几个变体(一个用于Pascal,一个用于Modula 2,一个用于Oberon),其中至少有一个可以从他的{{3 }}

二进制树并不总是二叉树搜索树,而二元树的一种用途是导出web site

无论哪种方式,二叉树都是通过从叶子分开开始构建的,并且在每一步中,将两个最不可能的子树连接成一个更大的子树,直到只剩下一个子树。为了在每个步骤中有效地选择两个最不可能的子树,使用优先级队列数据结构 - 可能是Huffman compression code

构建一次然后从未修改过的二叉树可以有多种用途,但可以有效更新的二叉树更有用。有一些权重平衡的二叉树数据结构,但我不熟悉它们。注意 - 通常使用术语“权重平衡”,其中每个节点始终具有权重1,但子树权重近似平衡。其中一些可能适用于不同的节点权重,但我不确定。

无论如何,对于数组中的二进制搜索,问题在于可能使用任意概率分布,但效率低下。例如,您可以拥有一个running-total-of-weights数组。对于二进制搜索的每次迭代,您需要确定中途概率分布点,因此确定其值,然后搜索running-total-of-weights数组。您可以获得主要二进制搜索的完美重量平衡的下一个选择,但您必须对运行的总数组进行完整的二进制搜索才能完成。

但是,如果您可以在不搜索已知概率分布的情况下确定加权中点,则该原则有效。原理是相同的 - 你需要概率分布的积分(替换运行的总数组),当你需要一个中点时,你选择它来获得积分的精确中心值。这更像是代数问题,而不是编程问题。

像这样的加权二分搜索的一个问题是最差情况下的性能更差 - 通常是由常数因素决定,但是,如果分布足够偏差,您可能最终会有效地进行线性搜索。如果您的假设分布是正确的,尽管偶尔会有慢速搜索,但平均情况下的性能会有所提高,但如果您的假设分布错误,那么当许多搜索针对根据该分布不太可能的项目时,您可以为此付费。在二叉树形式中,“不太可能”的节点比根在简单平衡(假设的平坦概率分布)二叉树中的节点更远。

平坦概率分布假设即使完全错误也能很好地工作 - 最坏的情况是好的,并且最佳和平均情况必须至少在定义上是好的。如果实际查询概率与您的假设有很大不同,那么从平面分布中移动得越远,情况就越糟糕。

答案 1 :(得分:4)

让我准确一点。你想要二进制搜索的是:

 Given array A which is sorted, but have non-uniform distribution
 Given left & right index L & R of search range
 Want to search for a value X in A

 To apply binary search, we want to find the index M in [L,R] 
 as the next position to look at.

 Where the value X should have equal chances to be in either range [L,M-1] or [M+1,R]

一般来说,你当然想要选择M,你认为X值应该在A中。 因为即使你错过了,总机会的一半也会被消除。

所以在我看来你对发行有一些期待。 如果你能告诉我们你的'1 / x发行'是什么意思,那么 也许有人可以帮助我建立你的建议。


让我举一个有效的例子。

我将使用@ 1 / x发行版的类似解释为@Leonid Volnitsky

这是一个生成输入数组A

的Python代码
from random import uniform

# Generating input
a,b = 10,20
A = [ 1.0/uniform(a,b) for i in range(10) ]
A.sort()

# example input (rounded)
# A = [0.0513, 0.0552, 0.0562, 0.0574, 0.0576, 0.0602, 0.0616, 0.0721, 0.0728, 0.0880]

假设要搜索的值是:

X = 0.0553

然后估计的X指数为:

= total number of items * cummulative probability distribution up to X
= length(A) * P(x <= X)

那么如何计算P(x <= X)? 这种情况很简单。 我们将X反转回[a,b]之间的值,我们称之为

X' = 1/X ~ 18

因此

P(x <= X) = (b-X')/(b-a)
          = (20-18)/(20-10)
          = 2/10

所以X的预期位置是:

10*(2/10) = 2

嗯,这非常准确!

重复预测A的每个给定部分中X的位置的过程需要更多的工作。但我希望这足以说明我的想法。

我知道可能不再是二进制搜索了 如果你只需一步就能得到答案。 但是承认,如果您知道输入数组的分布,这就是可以做的事情。

答案 2 :(得分:3)

二进制搜索的目的是,对于已排序的数组,每次数组的一半时,最小化最坏情况,例如:您可以执行的最差检查次数是log2(条目)。如果您进行某种“不均匀”二分搜索,将数组划分为越来越大的一半,如果元素总是在较大的一半,则可能会出现更糟糕的最坏情况行为。所以,我认为二进制搜索仍然是最好的算法,无论预期的分布如何,只是因为它具有最好的坏情况行为。

答案 3 :(得分:3)

你有一个条目向量,比如说[x1, x2, ..., xN],你知道在你拥有的向量上,查询的分布是以概率1/x给出的。这意味着您的查询将使用该分发进行,即在每次咨询时,您将更有可能获取元素xN

这会导致您的二进制搜索树在考虑您的标签时得到平衡,但不会对搜索强制执行任何策略。此策略的一个可能的变化是放宽平衡二叉搜索树的约束 - 在父节点左侧较小,向右较大 - 并且实际选择父节点作为具有较高概率的节点,以及他们的子节点是两个最可能的元素。

请注意,不是二进制搜索树,因为您不是在每个步骤中将搜索空间除以2,而是根据搜索模式分布重新平衡树。这意味着您最糟糕的搜索情况可能会达到O(N)。例如,拥有v = [10, 20, 30, 40, 50, 60]

        30
      /    \
    20      50
   /       /  \
 10       40   60

可以使用您的函数f(x) = 1 / x重新排序或重新平衡

f([10, 20, 30, 40, 50, 60]) = [0.100, 0.050, 0.033, 0.025, 0.020, 0.016]
sort(v, f(v)) = [10, 20, 30, 40, 50, 60]

进入新的搜索树,如下所示:

        10  -------------> the most probable of being taken
      /    \               leaving v = [[20, 30], [40, 50, 60]]
    20      30  ---------> the most probable of being taken
           /  \            leaving v = [[40, 50], [60]]
          40   50 -------> the most probable of being taken
              /            leaving v = [[60]]
             60

如果您搜索10,则只需要进行一次比较,但如果您正在寻找60,则会执行O(N)次比较,但不会将此作为二分搜索。正如@ Steve314所指出的那样,你从一棵完全平衡的树中走得越远,你最糟糕的搜索情况就越糟糕。

答案 4 :(得分:2)

我将从您的描述中假设:

  • X 均匀分布
  • Y=1/X是您要搜索的数据,它存储在已排序的表格中
  • 给定值 y ,您需要在上表中对其进行二进制搜索

二进制搜索通常使用范围中心(中位数)的值。对于均匀分布,可以通过了解表中我们需要查找搜索值的大致位置来加速搜索。

例如,如果我们在[0,1]范围内具有均匀分布的值且查询是0.25,则最好不要在范围的中心,而是在范围的第一个四分之一。

要对 1 / X 数据使用相同的技术,请在表格中存储 Y ,但反向 1 / Y 。不是搜索 y ,而是搜索反向值 1 / y

答案 5 :(得分:1)

对于预期条件下均匀分布的密钥,未加权的二进制搜索甚至不是最佳的,但它是最坏情况下的术语。

比例加权二进制搜索(我已经使用了几十年)为统一数据做了你想要的,并且通过对其他分布应用隐式或显式变换。排序的哈希表是密切相关的(我已经知道了几十年但从未打扰过尝试它)。

在本讨论中,我将假设数据是从1..N和1..N索引的大小为N的数组中统一选择的。如果它有不同的解决方案,例如一个Zipfian分布,其值与1 / index成比例,你可以应用反函数来展平分布,或者Fisher变换通常会有所帮助(见维基百科)。

最初你有1..N作为界限,但实际上你可能知道实际的Min..Max。在任何情况下,我们都假设我们当前搜索的索引范围[L..R]总是有一个闭区间[Min,Max],最初这是O(N)。 我们正在寻找关键K并希望索引I以便

[I-R] / [K-Max] = [L-I] / [Min-K] = [L-R] / [Min-Max],例如I = [R-L] / [Max-Min] * [Max-K] + L。

舍入以使较小的分区变大而不是较小(以帮助最坏的情况)。预期的绝对平均误差和均方根误差<&lt;√[R-L](基于Poisson / Skellam或随机游走模型 - 参见维基百科)。因此,预期的步数是O(loglogN)。

最坏的情况可以通过几种方式限制为O(logN)。首先,我们可以决定我们认为可接受的常量,可能需要步骤1.继续执行上述loglogN步骤,然后使用减半将为任何此类c实现此目的。

或者,我们可以修改对数的标准基数b = B = 2,因此b> 2。假设我们取b = 8,然后有效地c~b / B.然后我们可以修改上面的舍入,这样在步骤k,最大的分区必须至多为N * b ^ -k。如果我们从考虑每个步骤消除1 / b导致最坏情况b / 2 lgN,则跟踪预期的大小。然而,这将使我们的预期情况回到O(log N),因为我们只允许每次将小分区减少1 / b。在应用受限舍入之前,我们可以通过使用小分区的简单上下文来恢复O(loglog N)期望loglogN步骤。这是合适的,因为在预期在特定值的局部突发中,分布大致是均匀的(即对于任何平滑分布函数,例如在这种情况下,Skellam,任何足够小的段近似线性,其斜率由其导数给出)该部分的中心)。

至于排序的哈希,我以为我几十年前在Knuth中读过这个,但找不到参考。该技术涉及推动而不是探测 - (可能是加权二进制)搜索以找到正确的位置或间隙然后推开以根据需要腾出空间,并且散列函数必须遵守排序。这种推动可以环绕,因此需要第二次通过桌子才能将它们全部拾取 - 跟踪Min和Max及其索引(向前或向后排序列表从一个开始并循环跟踪到另一个;然后它们也可以代替1和N作为上面搜索的初始括号;否则1和N可以用作代理项。

如果负载系数α接近1,则预期插入O(√N)对于预期的O(√N)项目,其仍然平均摊销为O(1)。这个成本预计会随着指数而呈指数下降 - 我相信(在Poisson假设下)μ~σ~√[Nexp(α)]。

上述按比例加权的二进制搜索可用于改进初始探测。