【问题标题】:Efficient list intersection algorithm高效的列表交集算法
【发布时间】:2009-01-30 21:35:40
【问题描述】:

给定两个列表(不一定是排序的),找到这些列表的集合交集的最有效的非递归算法是什么?
我不相信我可以使用散列算法。

【问题讨论】:

  • 这听起来像是一道作业题——是吗?
  • 其实没有。我在工作,我必须在称为 eviews 的统计建模环境中编程。 Eviews 没有内置集合交集,也不支持递归。我需要一个快速算法,因为我的集合往往很大并且程序需要经常运行。谢谢!
  • 每个列表中的价值是唯一的吗?如果是,您可以加入列表,对结果进行排序,然后查找重复项。
  • 集合中通常有多少个元素? (例如,是否值得您花时间尝试实现哈希,或者您可以摆脱 sort = O(n log n) 吗?)
  • 您要排序的数据类型是什么?有时,您可以在设计算法时利用数据的某些特征。

标签: algorithm list set-intersection


【解决方案1】:

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

【讨论】:

  • 这听起来不错,但我不相信我也可以使用散列算法。有什么建议吗?
  • 那么,也许: * sort list1 (time: n log n) * sort list2 (time: n log n) * 合并两者并在同时迭代两个排序列表时检查相似的条目(线性时间)
  • 我没有足够的积分来评论其他线程,但是关于快速排序是递归的一点:您可以在没有递归的情况下实现它。见这里,例如:codeguru.com/forum/archive/index.php/t-333288.html
  • 如果您可以访问数组,那么您当然可以构建自己的哈希表。构建一个合理的哈希函数通常很简单。
  • 您如何处理多个列表?比如说,您有多个列表,并且想要对所有列表进行交集?在我的理解中,它仍然会这样:首先创建哈希,开始迭代其余列表,并检查它们的每个元素是否存在于哈希中......对吗?
【解决方案2】:

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

【讨论】:

  • 这是一种有效确定两个大集合是否重叠的迷人方法。
【解决方案3】:

没有散列,我想你有两个选择:

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

【讨论】:

  • 还有一个:如果可以为每个元素添加一个属性,请先将两个集合中的所有元素重置为零,然后将其中一个集合中的属性设置为 1,然后扫描第二组查找属性设置为 1 的元素。这是O(n + m),但并非总是可行。
  • 也许可以通过 O(log n) 二进制搜索来改进?
  • 请注意O(n lg n) * 2 + O(n) * 2O(n lg n) 相同。
  • 首先对链表进行排序不是 nlogn,因为您没有 O(1) 访问权限,您需要将其移动到数组中。其次,您只需对一个列表进行排序,然后对第一个列表的每个元素进行二分搜索。
【解决方案4】:

eviews features list 看来,它支持复杂的合并和连接(如果这是数据库术语中的“连接”,它将计算交集)。现在浏览您的文档:-)

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

【讨论】:

    【解决方案5】:

    使用 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)

    【讨论】:

    • 对于二叉搜索树部分,仍然需要对其中一个列表进行排序(这将增加 O(m log m) 或 O(n log n) 的复杂度)。不过,这仍然是一个非常有用的答案:就我而言,我有两个包含相同对象的列表,但每个列表都根据不同的对象属性进行排序 - 我需要获取两个列表中都有哪些对象。这个答案与每个列表排序的属性无关。谢谢!
    • 实际上,构建树是 O(n log n) 所以它是 O((n+m)log n) 整体
    【解决方案6】:

    在 C++ 中,可以使用 STL map 尝试以下操作

    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;
    }
    

    【讨论】:

      【解决方案7】:

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

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

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

      【讨论】:

      • 链表排序不能nlogn
      【解决方案8】:

      首先,使用快速排序对两个列表进行排序: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 是列表的大小。

      编辑:快速排序是递归的,如 cmets 所述,但看起来有 non-recursive implementations

      【讨论】:

      • 快速排序不是递归的吗?还是有它的非递归版本?
      • 我不会称之为 O(max(n,m))。你也在做两种事情。
      • 是否有非递归版本的合并排序也可以工作?
      • 堆排序是非递归的,但增加了一些数据结构的需求。可以吗?
      • 有一个非递归快速排序。将要排序的完整间隔推入堆栈。然后在一个循环中,弹出然后分区间隔。任何需要进一步排序的间隔都被推入堆栈。回到循环顶部,弹出分区......重复冲洗,直到堆栈为空。
      【解决方案9】:

      使用skip pointersSSE instructions可以提高列表交集效率。

      【讨论】:

        【解决方案10】:

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

        既然你事先对你的数据有一点了解,你应该能够选择一个好的散列函数。

        【讨论】:

          【解决方案11】:

          我支持“集合”的想法。在 JavaScript 中,您可以使用第一个列表来填充对象,使用列表元素作为名称。然后使用第二个列表中的列表元素,看看这些属性是否存在。

          【讨论】:

            【解决方案12】:

            如果内置支持sets(正如您在标题中所称),通常会有一个交集方法。

            无论如何,正如有人所说,如果您对列表进行了排序,您可以轻松完成(我不会发布代码,有人已经这样做了)。如果您不能使用递归,则没有问题。有quick sort recursion-less 实现。

            【讨论】:

            • 1.如果 eviews 支持集合,它可能会提供一种设置交叉点的方法 2. 在这里加入两个集合有什么帮助。交集是两个集合中的元素。当我在这里加入时,我想到了计算两组的并集
            • java 支持集合,但没有内置的交集函数。
            • @lensovet:如果它实现了 java.util.Set ,则有 java.util.Set.retainAll(Collection) 方法。其文档中写道:“如果指定的集合也是一个集合,则此操作有效地修改此集合,使其值为两个集合的交集。”
            【解决方案13】:

            在 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;
            }
            

            【讨论】:

            • 如果您在任何数组中有重复项,您将得到不正确的行为。
            【解决方案14】:

            从大哦符号的定义:

            T(N) = O(f(N)) 如果存在正常数 c 和 n 0 使得 当 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),说这两个列表大小相同。

            【讨论】:

              【解决方案15】:

              时间:O(n) 空间:O(1) 用于识别交叉点的解决方案。

              例如,两个给定节点将通过每次到达终点时交换指针来检测交点。 Video Explanation Here.

              public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
                  ListNode pA = headA;
                  ListNode pB = headB;
                  while (pA != pB) {
                      pA = pA == null ? headB : pA.next;
                      pB = pB == null ? headA : pB.next;
                  }
                  return pA;
              }
              

              谢谢。

              编辑

              我对交叉点的解释是找到交叉点

              例如:

              对于给定的列表 A 和 B,A 和 B 将在点 c1“相遇/相交”,上面的算法将返回 c1。正如 OP 所说,OP 无法访问 Hashmaps 或某种形式,我相信 OP 是说算法应该具有 O(1) 空间复杂度。

              我前段时间从 Leetcode 那里得到了这个想法,如果有兴趣:Intersection of Two Linked Lists

              【讨论】:

              • 从大卫的 cmets 到其他答案,似乎他正在寻找两个列表共有的所有元素。您能否在您的帖子中总结视频的信息,特别是对 intersection 使用的解释?
              • @greybeard 当然,我已经编辑了我的答案以满足您的需求。让我知道是否有任何令人困惑的地方。谢谢。
              猜你喜欢
              • 2020-11-05
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-11-14
              • 2010-09-27
              • 2011-06-17
              • 1970-01-01
              相关资源
              最近更新 更多