高效列表交集算法


76

给定两个列表(不一定排序),找到这些列表的交集的最有效的非递归算法是什么?


3
这听起来像是一个作业问题-是吗?
Erik Forbes,2009年

29
其实不行 我在工作,必须在称为eviews的统计建模环境中进行编程。Eviews没有内置的交集,也不支持递归。我需要一种快速算法,因为我的集合往往很大,并且程序需要经常运行。谢谢!

4
每个列表中的价目表是否唯一?如果是,则可以加入列表,对结果进行排序,然后查找重复项。
法比奥·塞孔内罗(Febio Ceconello)2009年

1
集合中通常有多少个元素?(例如,值得花时间尝试实现哈希,还是可以让排序= O(n log n)逃脱?)
Jason S,2009年

2
您要排序的数据类型是什么?有时,在设计算法时可以利用数据的特征。
AShelly

Answers:


42

您可以将第一个列表的所有元素放入哈希集中。然后,迭代第二个,并针对其每个元素,检查哈希以查看其是否存在于第一个列表中。如果是这样,请将其输出为相交的元素。


这听起来不错,但我也不相信我也可以使用哈希算法。有什么建议?

5
然后,也许:*排序列表1(时间:n log n)*排序列表2(时间:n log n)*合并两者,并在同时迭代两个排序列表(线性时间)时检查相似的条目
弗兰克·

2
我没有足够的观点来评论其他线程,但是考虑到快速排序是递归的:您可以实现它而无需递归。参见此处,例如:codeguru.com/forum/archive/index.php/t-333288.html
弗兰克

4
如果您有权访问数组,那么肯定可以构建自己的哈希表。构建合理的哈希函数通常非常简单。
基思·欧文

1
以及如何针对多个列表呢?假设您有多个列表,并且想要所有列表的交集?以我的理解,它仍然会这样:首先创建哈希,开始遍历其余列表,并检查其每个元素是否存在于哈希中。
可汗

24

您可能想看看Bloom过滤器。它们是位向量,可为元素是否为集合的成员提供概率性答案。集合交集可以通过简单的按位与运算来实现。如果您有大量的空交集,则布隆过滤器可以帮助您快速消除这些交集。但是,您仍然必须借助此处提到的其他算法之一来计算实际的交点。 http://en.wikipedia.org/wiki/Bloom_filter


这是一种有效确定两个大集合是否重叠的迷人方法。
里克·斯拉基

9

不带哈希,我想您有两个选择:

  • 天真的方法是将每个元素与每个其他元素进行比较。O(n ^ 2)
  • 另一种方法是先对列表进行排序,然后遍历它们:O(n lg n)* 2 + 2 * O(n)

还有一个:如果可以为每个元素添加一个属性,则首先将两个集合中的所有元素都重置为零,然后在其中一个集合中将其设置为1,然后遍历第二个集合以查找具有该属性集合的元素到1.这O(n + m)并非总是可能的。
罗曼·斯塔科夫

也许可以使用O(log n)二进制搜索进行改进?
骑士

6
只是一个注释,O(n lg n) * 2 + O(n) * 2与相同O(n lg n)
porglezomp '16

首先,因为没有O(1)访问权,所以对n链表进行排序不是nlogn,您需要将其移动到数组中。其次,您只需要排序一个列表,然后对第一个列表的每个元素进行二进制搜索。
shinzou

7

eviews功能列表中,它似乎支持复杂的合并和连接(如果按照数据库术语中的“ join”,它将计算交集)。现在浏览您的文档:-)

此外,eviews拥有自己的用户论坛-为什么不在那里询问_


6

使用set 1构建一个二叉搜索树,O(log n)并使用set2进行迭代并搜索BST m X O(log n)总数O(log n) + O(m)+O(log n) ==> O(log n)(m+1)


2
对于二叉搜索树部分,仍然需要对列表之一进行排序(这将增加O(m log m)或O(n log n)的复杂度)。但是,这仍然是一个非常有用的答案:就我而言,我有两个包含相同对象的列表,但是每个列表都根据不同的对象属性进行了排序-我需要获取两个列表中的对象。该答案与对每个列表进行排序的属性无关。谢谢!
意外事故

2
实际上,构建树是O(n log n),所以总体来说是O((n + m)log n)
c-urchin

6

在C ++中,可以使用STL映射尝试以下方法

vector<int> set_intersection(vector<int> s1, vector<int> s2){

    vector<int> ret;
    map<int, bool> store;
    for(int i=0; i < s1.size(); i++){

        store[s1[i]] = true;
    }
    for(int i=0; i < s2.size(); i++){

        if(store[s2[i]] == true) ret.push_back(s2[i]);

    }
    return ret;
}

3

这是我想出的另一种可能的解决方案,它在时间复杂度上采用O(nlogn),而没有任何额外的存储。您可以在这里查看https://gist.github.com/4455373

它是这样工作的:假设这些集合不包含任何重复,请将所有集合合并为一个并对它进行排序。然后循环遍历合并的集合,并在每次迭代中在当前索引i和i + n之间创建一个子集,其中n是Universe中可用集合的数量。我们在循环时寻找的是一个大小为n的重复序列,该序列等于宇宙中集合的数量。

如果i处的那个子集等于n处的那个子集,这意味着i处的元素重复了n次,这等于集合的总数。并且由于任何集合中都没有重复,这意味着每个集合都包含该值,因此我们将其添加到交集。然后我们将索引移动i + i和n之间剩余的值,因为这些索引中肯定没有一个会形成重复序列。


不能对链接列表进行排序
shinzou

2

首先,使用quicksort对两个列表进行排序:O(n * log(n)。然后,通过首先浏览最低值并添加公共值来比较列表,例如,在lua中):

function findIntersection(l1, l2)
    i, j = 1,1
    intersect = {}

    while i < #l1 and j < #l2 do
        if l1[i] == l2[i] then
            i, j = i + 1, j + 1
            table.insert(intersect, l1[i])
        else if l1[i] > l2[j] then
            l1, l2 = l2, l1
            i, j = j, i
        else
            i = i + 1
        end
    end

    return intersect
end

这是列表的O(max(n, m))位置nm大小。

编辑:快速排序是递归的,如评论中所述,但似乎有非递归 实现


quicksort不是递归的吗?还是有非递归版本?

我不会称之为O(max(n,m))。您也在做两种。
汤姆·里特

是否有非递归版本的mergesort也可以工作?

Heapsort是非递归的,但是增加了一些数据结构的需求。可以吗?
Wookai

1
有一个非递归的快速排序。将整个间隔推入堆栈。然后循环播放,然后对时间间隔进行分区。需要进一步排序的所有时间间隔都将推入堆栈。返回循环顶部,弹出分区...重复冲洗,直到堆栈为空。
EvilTeach


1

为什么不实现自己的简单哈希表或哈希集?如果您的列表很大,那么避免nlogn交叉是值得的。

由于您已经对数据有所了解,因此您应该能够选择一个好的哈希函数。


1

我赞成“设置”的想法。在JavaScript中,您可以使用第一个列表来填充对象,并将列表元素用作名称。然后,使用第二个列表中的列表元素,查看这些属性是否存在。


1

如果有内置的对的支持(您在标题中称呼它们),通常会有一个交集方法。

无论如何,就像有人说的那样,如果您对列表进行了排序,那么您可以轻松地做到这一点(我不会张贴代码,有人已经这样做了)。如果您不能使用递归就没有问题。有快速排序的无递归实现。


1.如果eviews支持集,则可能会提供一种集相交的方法。2.如何连接两个集在这里有帮助。相交是两个集合中的那些元素。当我在这里加入时,我想到了计算两个集合的并集
f3lix

Java支持集合,但没有内置的交集函数。
lensovet 2010年

2
@lensovet:如果它实现java.util.Set,则有java.util.Set.retainAll(Collection)方法。它的文档显示为:“如果指定的集合也是一个集合,则此操作会有效地修改此集合,以使其值为两个集合的交集。”
安德里亚·安布

0

我得到了一些很好的答案,从这个,你可能能够应用。我还没有机会尝试它们,但是由于它们也涵盖了交叉路口,因此您可能会发现它们很有用。


0

在PHP中,类似

function intersect($X) { // X is an array of arrays; returns intersection of all the arrays
  $counts = Array(); $result = Array();
  foreach ($X AS $x) {
    foreach ($x AS $y) { $counts[$y]++; }
  }
  foreach ($counts AS $x => $count) {
    if ($count == count($X)) { $result[] = $x; }
  }
  return $result;
}

1
如果您在任何阵列中都有重复项,则会得到错误的行为。
Slawek

0

根据Big-Oh符号的定义:

如果存在正常数c和n 0,则T(N)= O(f(N)),使得当N≥n 0时T(N)≤cf(N)。

在实践中,这意味着如果两个列表的大小相对较小,则在两个for循环中少写100个元素就可以了。循环第一个列表,然后在第二个列表中查找相似的对象。就我而言,它工作得很好,因为列表中的最大元素数不会超过10-20。但是,一个好的解决方案是对第一个O(n log n)进行排序,对第二个O(n log n)进行排序,然后将它们合并,另一个O(n log n)大致得出O(3 n log n),说这两个列表的大小相同。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.