我有两个列表,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])
答案 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次。
对于L和M,每个包含3438个项目,值范围从1到34380,并且Larsen的哈希表的加载因子为0.75,运行的中位数为:
差异比我预期的要大得多(而且,我承认,不是我预期的方向)。我怀疑我在实施中犯了一个或多个重大错误。如果有人发现一个,我真的很想听听它!
有一点是我在定时方法中分配了Larsen的哈希表。因此,它支付分配和(一些)垃圾收集的成本。我认为这是公平的,因为它只是算法所需的临时结构。如果你认为它是可以重用的东西,那么将它移动到一个实例字段并且只分配一次(和Arrays。在定时方法中将其填充为零)就足够简单了,看看它如何影响性能。
答案 2 :(得分:1)
您可以以M
为代价创建哈希集,而不是以n * log(n)
为代价对n
进行排序。
您还可以在迭代时将所有总和存储在另一个哈希集中,并添加一个检查以确保您不会执行相同的搜索两次。
答案 3 :(得分:0)
除了排序的M数组之外,您可以使用散列表来避免二进制搜索。
答案 4 :(得分:0)
或者,将L的所有成员添加到散列集lSet,然后迭代M,对M中的每个m执行以下步骤:
这将需要更少的迭代,代价是更多的内存。您将需要为结构预先分配内存,如果这样可以提高速度。