我有一个包含20个数字(64位整数)的数组,如10,25,36,43 ......,118,121(已排序的数字)。
现在,我必须提供数百万的数字作为输入(比如17,30)。
我必须提供的是:
for Input 17:
17 is < 25 and > 10. So, output will be index 0.
for Input 30:
30 is < 36 and > 25. So, output will be index 1.
现在,我可以使用线性搜索,二进制搜索来实现。有没有什么方法可以更快地完成它?输入数字是随机的(高斯)。
答案 0 :(得分:6)
如果您知道发布内容,则可以以更智能的方式指导您的搜索。
以下是二元搜索的这种变体的粗略概念:
假设您的数据预计将在0到100之间统一分配。
如果您观察到值0,则从头开始。如果您的值为37,则从您拥有的数组的37%开始。 这是二元搜索的主要区别:你并不总是从50%开始,但是你试图从预期的“最佳”位置开始。
这也适用于高斯分布式数据,如果您知道参数(如果您不知道它们,您仍然可以从观察到的数据中轻松估算它们)。您将计算高斯CDF,这将产生开始搜索的位置。
现在进行下一步,您需要优化搜索。在你看到的位置,有一个不同的价值。您可以使用它来重新估计位置以继续搜索。
现在即使你不知道发行,这也可以很好地发挥作用。所以你从二进制搜索开始,并且已经查看了50%和25%的对象。如果您的查询值是例如,那么可以做出更好的猜测,而不是接下来达到37.5%非常接近50%的入场券。除非你的数据集非常“笨拙”(并且你的查询与数据没有关联),否则这应该仍然优于“天真”的二进制搜索,它总是在中间分裂。
http://en.wikipedia.org/wiki/Interpolation_search
预期的平均运行时间显然是O(log(log(n))
,来自维基百科。
更新:因为有人抱怨只有20个号码,所以情况有所不同。对,他们是。 使用20个数字线性搜索可能是最佳。因为CPU缓存。通过少量内存进行线性扫描 - 适合CPU缓存 - 可以非常快。特别是展开循环。但是那个案子非常可怜而且无趣。恕我直言。
答案 1 :(得分:2)
由于您的数组已排序,因此没有什么比binary search
更好。
线性搜索为O(n)
,而二进制搜索为O(log n)
修改强>
插值搜索进行了额外的假设(元素必须均匀分布)并且每次迭代进行更多的比较。
您可以尝试两者并根据实际情况衡量哪种情况更适合您的情况
答案 2 :(得分:2)
我认为最好的选择是使用upper_bound - 它会发现数组中的第一个值大于您要搜索的值。
仍然取决于您尝试解决的问题,lower_bound或binary_search可能是您需要的。
所有这些算法都具有对数复杂度。
答案 3 :(得分:2)
事实上,这个问题非常有趣,因为它是信息理论框架的重新演绎。
给定20个数字,最终会有21个分箱(包括&lt;第一个和&gt;最后一个)。
对于每个传入号码,您要映射到这21个箱子中的一个。这种映射是通过比较完成的。每次比较都会给出1位信息(&lt;或&gt; = - 两种状态)。
因此,假设传入的数字需要进行5次比较以确定它属于哪个bin,那么它相当于使用5位来表示该数字。
我们的目标是尽量减少比较次数!我们有100万个数字,每个数字属于21个有序代码字。我们怎么做?
这正是熵压缩问题。
让[1],... a [20]成为你的20个数字。
设p(n)= pr {输入数是&lt; n}。
按如下方式构建决策树。
Step 1.
let i = argmin |p(a[i]) - 0.5|
define p0(n) = p(n) / (sum(p(j), j=0...a[i-1])), and p0(n)=0 for n >= a[i].
define p1(n) = p(n) / (sum(p(j), j=a[i]...a[20])), and p1(n)=0 for n < a[i].
Step 2.
let i0 = argmin |p0(a[i0]) - 0.5|
let i1 = argmin |p1(a[i1]) - 0.5|
依旧......
当我们完成时,我们最终得到:
i, i0, i1, i00, i01, i10, i11, etc.
这些中的每一个都给我们比较位置。
现在我们的算法如下:
让你=输入数字。
if (u < a[i]) {
if (u < a[i0]) {
if (u < a[i00]) {
} else {
}
} else {
if (u < a[i01]) {
} else {
}
}
} else {
similarly...
}
所以我定义了一个树,if语句在树上行走。我们也可以把它放到一个循环中,但用一堆if来更容易说明。
所以,例如,如果您知道您的数据均匀分布在0到2 ^ 63之间,并且您的20个数字
0,1,2,3,...19
然后
i = 20 (notice that there is no i1)
i0 = 10
i00 = 5
i01 = 15
i000 = 3
i001 = 7
i010 = 13
i011 = 17
i0000 = 2
i0001 = 4
i0010 = 6
i0011 = 9
i00110 = 8
i0100 = 12
i01000 = 11
i0110 = 16
i0111 = 19
i01110 = 18
好的,基本上,比较如下:
if (u < a[20]) {
if (u < a[10]) {
if (u < a[5]) {
} else {
...
}
} else {
...
}
} else {
return 21
}
所以请注意,我不是在做二分搜索!我首先检查终点。为什么呢?
有100 *((2 ^ 63)-20)/(2 ^ 63)%的几率大于[20]。这基本上就像99.999999999999999783159565502899%的几率!
因此,对于具有上述指定属性的数据集,此算法具有预期的1比较数! (这比log log更好:p)
注意我在这里所做的是基本上使用较少的比较来查找更可能的数字,并且找到更不可能的数字。例如,数字18需要6次比较(比二分搜索需要多1次);但是,数字20到2 ^ 63只需要1次比较。同样的原理用于无损(熵)数据压缩 - 使用较少的位来编码经常出现的代码字。
构建树是一次性过程,您可以在一百万次后使用树。
问题是......这个决策树何时成为二元搜索?做作业练习! :答案很简单。它类似于你不能再压缩文件的时候了。
好的,所以我没有把它拉出我的背后......基础在这里:答案 4 :(得分:1)
您可以使用std::lower_bound和std::upper_bound执行二进制搜索。这些可以返回迭代器,因此您可以使用std::distance
来获取索引。