这种搜索算法是否最佳?

时间:2011-04-06 12:17:39

标签: algorithm language-agnostic optimization search

我有两个列表,L和M,每个列表包含数千个64位无符号整数。我需要弄清楚L中任何两个成员的总和本身是否是M的成员。

是否可以改进以下算法的性能?

Sort(M)
for i = 0 to Length(L)
    for j = i + 1 to Length(L)
        BinarySearch(M, L[i] + L[j])

5 个答案:

答案 0 :(得分:2)

问题中示例代码的复杂性是O(m log m + l 2 log m)其中l = | L |并且m = | M |因为它为L(O(l 2 ))中的每对元素运行二元搜索(O(log m)),并且M首先排序。

使用哈希表替换二进制搜索会将复杂性降低到O(l 2 ),假设哈希表插入和查找是O(1)操作。

这是渐近最优的,只要你假设你需要处理列表L上的每对数字,因为有O(l 2 )这样的对。如果L上有数千个数字,并且它们是随机的64位整数,那么你肯定需要处理所有的数组。

答案 1 :(得分:2)

(我假设您的目标是在L中找到所有对,并将其与M中的某些内容相加)

忘记哈希表!

对两个列表进行排序。

然后执行算法的外循环:遍历L中的每个元素,然后遍历L中的每个更大的元素。当你去的时候,形成总和并检查它是否在M中。

但不要使用二分法搜索:只需从您查看的最后一个位置进行线性扫描。假设你正在研究某个值i,你有一些值j,接着是一些值j'。当搜索(i + j)时,你将得到M中找到该值的点,或者第一个最大值。你现在正在寻找(i + j');因为j'> j,你知道(i + j')> (i + j),所以它不能比你在最后一个地方更早出现。如果L和M都是平滑分布的,那么很有可能M中你找到的点(i + j')只有一点距离。

如果数组不是平滑分布的,那么比线性扫描更好的是某种跳跃扫描 - 一次向前看N个元素,如果跳跃太远则将N减半。

我相信这个算法是O(n ^ 2),它与任何提出的哈希算法一样快(它具有O(1)原语操作,但仍然必须做O(n ** 2)。这也意味着您不必担心要对O(n log n)进行排序。它具有比哈希算法更好的数据局部性 - 它基本上由数组上的成对流读取组成,重复n次。


编辑:我编写了Paul Baker的原始算法,Nick Larsen的哈希表算法,我的算法和简单的基准测试框架。实现很简单(哈希表中的线性探测,在我的线性搜索中没有跳过),我不得不猜测各种大小参数。有关代码,请参阅http://urchin.earth.li/~twic/Code/SumTest/。我欢迎有关任何实现,框架和参数的更正或建议。

对于L和M,每个包含3438个项目,值范围从1到34380,并且Larsen的哈希表的加载因子为0.75,运行的中位数为:

  • Baker(二分查询):423 716 646 ns
  • Larsen(散列表):733 479 121 ns
  • Anderson(线性搜索):62 077 597 ns

差异比我预期的要大得多(而且,我承认,不是我预期的方向)。我怀疑我在实施中犯了一个或多个重大错误。如果有人发现一个,我真的很想听听它!

有一点是我在定时方法中分配了Larsen的哈希表。因此,它支付分配和(一些)垃圾收集的成本。我认为这是公平的,因为它只是算法所需的临时结构。如果你认为它是可以重用的东西,那么将它移动到一个实例字段并且只分配一次(和Arrays。在定时方法中将其填充为零)就足够简单了,看看它如何影响性能。

答案 2 :(得分:1)

您可以以M为代价创建哈希集,而不是以n * log(n)为代价对n进行排序。

您还可以在迭代时将所有总和存储在另一个哈希集中,并添加一个检查以确保您不会执行相同的搜索两次。

答案 3 :(得分:0)

除了排序的M数组之外,您可以使用散列表来避免二进制搜索。

答案 4 :(得分:0)

或者,将L的所有成员添加到散列集lSet,然后迭代M,对M中的每个m执行以下步骤:

  1. 将m添加到hashset mSet - 如果m已经在mSet中,则跳过此迭代;如果m在hashset dSet中,也跳过此迭代。
  2. 从m中减去L小于m的每个成员l得到d,并测试d是否也在lSet中;
  3. 如果是这样,将(l,d)添加到某个集合rSet;将d添加到hashset dSet。
  4. 这将需要更少的迭代,代价是更多的内存。您将需要为结构预先分配内存,如果这样可以提高速度。