【问题标题】:Algorithm to find if two sets intersect查找两个集合是否相交的算法
【发布时间】:2008-10-29 01:56:15
【问题描述】:

假设我有两个数组:

int ArrayA[] = {5, 17, 150, 230, 285};

int ArrayB[] = {7, 11, 57, 110, 230, 250};

两个数组都是排序的,可以是任意大小。我正在寻找一种有效的算法来查找数组之间是否包含任何重复的元素。我只想要一个正确/错误的答案,我不在乎共享哪个元素或共享多少。

天真的解决方案是遍历 ArrayA 中的每个项目,并在 ArrayB 中为它执行binary search。我相信这个复杂度是 O(m * log n)。

因为两个数组都是排序的,看起来应该有一个更有效的算法。

我还想要一个不假设数组包含数字的通用解决方案(即该解决方案也应该适用于字符串)。但是,比较运算符定义明确,两个数组都按从小到大排序。

【问题讨论】:

  • 顺便说一句,我们说您在此处概述的解决方案的复杂性是 O(m * log n),其中 m 和 n 是两个数组的大小。
  • 我有一种感觉,就是这样。谢谢。

标签: algorithm language-agnostic


【解决方案1】:

假装您正在执行合并排序,但不要将结果发送到任何地方。如果您到达任一来源的尽头,则没有交集。每次比较每个的下一个元素,如果相等,就有一个交集。

例如:

counterA = 0;
counterB = 0;
for(;;) {
    if(counterA == ArrayA.length || counterB == ArrayB.length)
        return false;
    else if(ArrayA[counterA] == ArrayB[counterB])
        return true;
    else if(ArrayA[counterA] < ArrayB[counterB])
        counterA++;
    else if(ArrayA[counterA] > ArrayB[counterB])
        counterB++;
    else
        halt_and_catch_fire();
}

【讨论】:

  • 如果不是很明显,这个解决方案是 O(n)
  • 顺便说一句,这对于通用代码的 C++ 迭代器非常有用。这让我觉得 STL 应该已经提供了一个解决方案……
  • 一个小问题:我鄙视无限循环。而不是“for(;;)”,这应该是“while(counterA != ArrayA.length && counterB != ArrayB.length)”(消除第一个 if())
  • 实际上,它是 O(n+m) 并且没有办法解决这个问题。考虑数组 [1,2,3...99, 100] 和 [50, 101]。它必须在终止之前查看所有 102 个数组元素。
  • 小问题:O(n) === O(m + n) - 大 O 表示法适用于 orders of complexity of algorithms,不是绝对度量。 O(n) 简单地说算法是线性的——你将对每个元素迭代一次。 n 的大小无关紧要。
【解决方案2】:

因为有人想知道 stl.开箱即用的 set_intersection 算法会做的比你想要的更多:它会找到所有的公共值。

    #include <vector>
    #include <algorithm>
    #include <iterator>
    using namespace std;
//    ...    
      int ArrayA[] = {5, 17, 150, 230, 285};
      int ArrayB[] = {7, 11, 57, 110, 230, 250};
      vector<int> intersection;
      ThrowWhenWritten output_iterator;
        set_intersection(ArrayA, ArrayA + sizeof(ArrayA)/sizeof(int),
                         ArrayB, ArrayB + sizeof(ArrayB)/sizeof(int),
                         back_insert_iterator<vector<int> >(intersection));

        return !intersection.empty();

这在 O(m+n) 时间内运行,但它需要存储所有重复项,并且在找到第一个 dup 时不会停止。

现在,修改stl的gnu implementation的代码,我们可以更准确的得到你想要的。

 template<typename InputIterator1, typename InputIterator2>
 bool 
 has_intersection(InputIterator1 first1, InputIterator1 last1,
             InputIterator2 first2, InputIterator2 last2)
    {
       while (first1 != last1 && first2 != last2) 
       {
          if (*first1 < *first2)
             ++first1;
          else if (*first2 < *first1)
             ++first2;
          else
             return true;
       }
       return false;
}

【讨论】:

  • 很好很简单,虽然我不会使用您从 GNU 复制的名称,但允许 STL 实现使用这些符号,但不允许 POD(普通旧开发人员)使用(双下划线和下划线大写被解析为实现)。
【解决方案3】:

如果一个列表比另一个短得多,则二分查找是可行的方法。如果列表的长度相似并且您对 O(m+n) 感到满意,那么标准的“合并”就可以了。有更灵活的更高级的算法。我在自己的搜索中遇到的一篇论文是:

http://www.cs.uwaterloo.ca/~ajsaling/papers/paper-spire.pdf

【讨论】:

    【解决方案4】:

    如果您不关心内存消耗,您可以通过使用哈希来获得良好的性能,即使用键 = 一个数组的值创建哈希,并针对该哈希测试第二个数组的值

    【讨论】:

    • 散列两个数组中较小的一个以节省最多的内存。这个解决方案肯定会很快。
    【解决方案5】:

    如果您使用的是 C# 3.0,那么为什么不在此处利用 LINQ?

    ArrayA.Intersect(ArrayB).Any()
    

    这不仅是通用的(适用于任何可比较的类型),其底层实现也非常高效(使用散列算法)。

    【讨论】:

      【解决方案6】:

      如果值的范围很小,您可以为其中一个构建查找表(时间成本 = O(N)),然后检查该位是否从另一个列表中设置(时间成本 = O(N) )。如果范围很大,你可以用哈希表做类似的事情。

      Glomek 的归并排序技巧是一个更好的主意。

      【讨论】:

        【解决方案7】:

        Glomek 是在正确的轨道上,但有点掩盖了算法。

        首先比较 ArrayA[0] 和 ArrayB[0]。如果他们是平等的,你就完成了。 如果 ArrayA[0] 小于 ArrayB[0],则移动到 ArrayA[1]。 如果 ArrayA[0] 大于 ArrayB[0],则移动到 ArrayB[1]。

        继续遍历,直到到达一个数组的末尾或找到匹配项。

        【讨论】:

          猜你喜欢
          • 2014-03-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-06-15
          相关资源
          最近更新 更多