【问题标题】:find the intersection of sets找到集合的交集
【发布时间】:2013-03-28 05:28:52
【问题描述】:

给定 n 组不同大小的整数。每个集合也可以包含重复项。我必须找到集合的交集。如果一个元素在所有集合中多次出现,则应将其添加到结果中。

例如,假设有三个集合 {0,5,5,3,4} {5,2,3,5,6} {1,3,5,5,6}。给定集合的交集应该是 {3,5,5}

我的做法是:

1.对数组进行排序。

2.从最小数组开始比较每个元素并更新计数。

有没有更有效的方法来找到交叉点?

【问题讨论】:

  • 这似乎非常接近最佳状态。
  • 在数学上,集合不包含重复; multisets 或 bag 可以包含重复项。
  • 对于多核,也许是并行插入排序(当然,您的数据必须足够大,才值得这样做)。
  • {0,5,5,3,4}、{1,3,5,5,6} 和 {3,5,5} 不是集合。如果您真的处理集合,最有效的实现是使用位数组。
  • “有没有更有效的方法来找到交叉点?” - 确实是的;看我的回答。

标签: c arrays algorithm


【解决方案1】:

如果你的“集合”只包含小整数,那么它们可以用一个计数数组来表示......例如,{5,2,3,5,6} 是

index 0 1 2 3 4 5 6
count 0 0 1 1 0 2 1

这些集合的交集是计数的最小值:

      index 0 1 2 3 4 5 6
            -------------
{0,5,5,3,4} 1 0 0 1 1 2 0
{5,2,3,5,6} 0 0 1 1 0 2 1
{1,3,5,5,6} 0 1 0 1 0 2 1  
min         0 0 0 1 0 2 0 = {3,5,5}

如果值不是小整数但它们很少,只需保留一个值数组 - 作为值和小整数之间的映射,小整数是数组的索引。

如果值太多以至于为每个集合设置一个计数数组太昂贵,请使用从值到计数的映射来表示每个“集合”,以及值的数组......然后迭代数组来生成每个值,遍历地图以获取计数并计算它们的最小值。为此,您将需要一个哈希表或二叉树库来实现映射......或者使用任何一种比 C 更现代的语言来提供这种集合类型。

【讨论】:

    【解决方案2】:

    例如,您可以为每个数组创建一个字典,遍历每个数组并将其添加到它们的计数器中,并添加到一个“全局”字典中,以确定是否检测到新数字。然后,您从“全局”字典中选择下一个数字(保证至少存在于一个计数器字典中),然后您得到所有​​计数器中的最小值。当然,如果您在单个字典中遇到 null,则此数字不会添加到结果中。否则,将“数字”的“最小找到”数量添加到结果数组中。使用这样的字典结构,算法的完整复杂度约为O(n*m),其中 M 是集合大小的最大值,N 是它们的数量,而如果你对集合进行排序,复杂度是 O(n*m*log(m)),如果您的集合包含 1000 多个元素。

    【讨论】:

    • 我认为将集合的数量乘以最大集合容量是不正确的,因为您最终会添加比现有数量更多的东西,我会说 O(n) 其中 n: 数量所有集合中的元素
    • @KhaledAKhunaifer 我们必须查询这些集合中的每个元素才能正确形成结果,它们最多是n*m,所以我们不能得到更少的 O() 函数比这个。 M 不是“设定容量”,它是算法开始时给出的最大值。集合容量可以大到 2^32,集合本身的大小为 5,如示例中所示。
    • 在 map-of-counts 中同时使用 m 表示袋子大小和不同键的数量是令人困惑的。
    【解决方案3】:

    这是我的代码,在 C99 中编译别忘了先实现 get、insert、remove 函数):

    struct MyNode { MyNode * next; int value; int frequency; }
    
    // returns MyNode pointer when value exist
    MyNode * get(MyNode * head, int val);
    
    // insert a new value, with frequency = 1
    void insert(MyNode * head, int val);
    
    // remove an element from the linked-list
    bool remove(MyNode * head, int val);
    
    int * intersection (int ** set, int w, int * h)
    {
        MyNode * head = 0;
        MyNode * temp = 0;
        int finalSize = 0;
        int k = 0;
    
        for (int i=0; i<w; i++)
        {
            for (int j=0; j<h[i]; j++)
            {
                temp = get(head, set[i][j]);
    
                if (temp == 0)
                {
                    insert(head, set[i][j]);
                    finalSize++;
                }
                else
                {
                    temp->frequency++;
                }
            }
        }
    
        temp = head;
        while (temp != 0)
        {
            if (temp->frequency != w)
            {
                temp = temp->next;
                remove(head, temp->value);
                finalSize--;
            }
            else
                temp = temp->next;
        }
    
        int * intersection = (int*)malloc(finalSize*sizeof(int));
    
        temp = head;
        while (temp != 0)
        {
            intersection[k++] = temp->data;
            temp = temp->next;
        }
    
        return intersection;
    }
    

    【讨论】:

      【解决方案4】:

      我建议对您的解决方案的唯一优化是将您的数组(它们不是真正的集合,因为它们有重复项)转换为键值字典,以便键是数组的元素,值是出现的次数。对于您的测试示例:{0,5,5,3,4} {5,2,3,5,6} {1,3,5,5,6} 字典看起来像这样

      {0 => 1, 3 => 1, 4 => 1, 5 => 2}
      {2 => 1, 3 => 1, 5 => 2, 6 => 1}
      {1 => 1, 3 => 1, 5 => 2, 6 => 1}
      

      然后,您从最小的字典开始比较字典对,如果元素同时出现在两个字典中 - 您会选择出现次数较少的字典。 这种优化将节省处理重复项所需的时间。

      生成的字典将是:{3 => 1, 5 => 2} - 您可以将其转换回数组。

      【讨论】:

        【解决方案5】:

        其他人已经涵盖了由arrays of counts, or maps of counts 表示每个“集合”(或更正式地说,“包”)的想法。如果有很多重复,并且每个包没有那么多钥匙,这将特别有用。给定 N 个包,每个包有 M 个元素,其中 K 个是不同的,转换为数组/映射表示和生成结果的复杂度将是 O(N x M) + O(N x K)。请注意,重复寻找 B 袋的交叉点只需花费O(B x K),因为您可以重复使用地图表示。

        如果您成对的交叉点正确排序,您还可以获得很多效率。例如,如果其中一个袋子只包含一个元素,则只有两种可能的答案:或者所有其他袋子也包含该元素(结果是该元素本身),或者至少其中一个不包含。这将允许您完全忽略其他集合的其余内容。在这种极端情况下,实际交叉点的运行时间将下降到 O(N),这是 K 倍的改进。

        一般来说,如果袋子的独特元素数量差异很大,则通过增加尺寸(独特元素的数量)对其地图进行排序会增加 O(N log N) 的成本,但允许您跳过很多计算交点时的键,将交点时间减少到O(N x K_min),其中K_min 是最小唯一元素计数的大小。

        在数据库查询优化过程中会做类似的事情,以大大缩短查询时间。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-11-26
          相关资源
          最近更新 更多