找到集合的交集

时间:2013-03-28 05:28:53

标签: c arrays algorithm

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

例如,考虑有三组{0,5,5,3,4} {5,2,3,5,6} {1,3,5,5,6}。给定集合的交集应为{3,5,5}

我的方法是:

1.排序数组。

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

找到交叉点是否有更有效的方法?

5 个答案:

答案 0 :(得分:3)

如果你的“集合”只包含小整数,那么它们可以用一系列计数表示......例如,{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更多的现代语言中的任何一种语言来提供这样的集合类型。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

这是我的代码,在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;
}

答案 3 :(得分:0)

我建议你的解决方案唯一的优化是将你的数组(它们不是真正的集合,因为它们有重复)转换为键值字典,这样键就是数组的元素,值将是发生的次数。对于您的测试示例:{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 =&gt; 1,5 =&gt; 2} - 你可以将它转换回数组。

答案 4 :(得分:0)

其他人已经涵盖了用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是最小唯一元素数的大小。

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