在数组中查找数字

时间:2013-02-09 08:44:38

标签: c++ algorithm search

我有一个包含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.

现在,我可以使用线性搜索,二进制搜索来实现。有没有什么方法可以更快地完成它?输入数字是随机的(高斯)。

5 个答案:

答案 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_boundbinary_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次比较。同样的原理用于无损(熵)数据压缩 - 使用较少的位来编码经常出现的代码字。

构建树是一次性过程,您可以在一百万次后使用树。

问题是......这个决策树何时成为二元搜索?做作业练习! :答案很简单。它类似于你不能再压缩文件的时候了。

好的,所以我没有把它拉出我的背后......基础在这里:

http://en.wikipedia.org/wiki/Arithmetic_coding

答案 4 :(得分:1)

您可以使用std::lower_boundstd::upper_bound执行二进制搜索。这些可以返回迭代器,因此您可以使用std::distance来获取索引。