所有n个集合的交集组合

9

我需要帮助找到一种高效的算法来解决这个问题:

Given n unsorted sets of integers, find all possible combinations of n and their intersections. For example:

Input (n=3):

Set 1 = 1, 10, 6, 11, 14, 3
Set 2 = 3, 7, 11, 9, 5
Set 3 = 11, 6, 9, 1, 4

Output:

Set 1 & 2: 3, 11
Set 1 & 3: 1, 6, 11
Set 2 & 3: 9, 11
Set 1, 2, & 3: 11
我考虑先找出所有可能的集合组合,然后使用算法来找到这里找到的 n 个集合的交集:n个集合的交集。但我担心这种方法的时间效率。
如果您能找到比我的天真方法更好的解决方案,有伪代码的答案会很有帮助。
3个回答

25

这里有一种方法受到MapReduce:大型集群上简化的数据处理的启发,如果需要的话可以通过分布式方式实现。

将所有元素映射到一组对[元素,集合]中。按元素分组此列表。您只需排序并提取元素即可。或者,您可以创建一个散列数组,其键是元素,值是找到该元素的集合列表。然后对于每个元素,发出一个[集合的组合,元素]列表。按组合分组。对于每个非空组合,您现在可以轻松读取其中的元素。

如果您希望使用真正的map-reduce来分发计算,第一个映射将映射到元素键和集合值。第一个缩小仅存储所在集合的集合列表。第二个映射会发出每个元素的一个键,表示它所在的每个集合组合,并以该值作为元素。第二次缩小将存储最终答案。

详细说明您的示例可能有所帮助。您从以下内容开始:

Set 1 = 1, 10, 6, 11, 14, 3
Set 2 = 3, 7, 11, 9, 5
Set 3 = 11, 6, 9, 1, 4

您将其映射到列表中:

[1, Set 1], [10, Set 1], [6, Set 1], [11, Set 1], [14, Set 1], [3, Set 1],
[3, Set 2], [7, Set 2], [11, Set 2], [9, Set 2], [5, Set 2],
[11, Set 3], [6, Set 3], [9, Set 3], [1, Set 3], [4, Set 3],

现在按元素分组 (我手动排序实现) 得到:

1: Set 1, Set 3
3: Set 1, Set 2
4: Set 3
5: Set 2
6: Set 1, Set 3
7: Set 2
9: Set 2, Set 3
10: Set 1
11: Set 1, Set 2, Set 3
14: Set 1

现在我们进行第二次映射(跳过仅出现在一个集合中的元素),得到:

[(Set 1, Set 3), 1],
[(Set 1, Set 2), 3],
[(Set 1, Set 3), 6],
[(Set 2, Set 3), 9],
[(Set 1, Set 2), 11],
[(Set 1, Set 3), 11],
[(Set 2, Set 3), 11],
[(Set 1, Set 2, Set 3), 11]

通过集合的组合,我们得到:

(Set 1, Set 2): 3, 11
(Set 1, Set 3): 1, 6, 11
(Set 2, Set 3): 9, 11
(Set 1, Set 2, Set 3): 11

1
使用您的示例,只需注意:

1 n 2 n 3

也是

(1 n 2) n 3

因此,如果您缓存了(1 n 2)的解决方案,您可以重复使用它,这样计算1 n 2 n 3将仅需要一个附加的交集计算。通常,如果有像您的示例中一样四个可能的集合组合,则如果保存和重用先前的结果,则最终只需要进行四个交集计算。

0

Java 10 实现

简述:首先为每个元素收集一个交叉点的映射 Map<Integer,Set<Integer>>,然后为此映射收集交叉点的交叉点的映射 Map<Set<Integer>,Set<Integer>>,最后将较大的交叉点集附加到与之相交的较小的交叉点集中。

// List<Set<Integer>>
var setList = List.of(
        Set.of(1, 10, 6, 11, 14, 3),
        Set.of(3, 7, 11, 9, 5),
        Set.of(11, 6, 9, 1, 4));

// TreeMap<Integer,List<Map.Entry<TreeSet<Integer>,TreeSet<Integer>>>>
var map = IntStream
        // iterate over indices of
        // the list elements, i.e. sets
        .range(0, setList.size())
        // for each set iterate over its elements
        // and map pairs 'element=set'
        .mapToObj(i -> setList.get(i).stream()
                // key - element, value - index
                // of the set starting from '1'
                .map(e -> Map.entry(e, i + 1)))
        // Stream<Map.Entry<Integer,Integer>>
        .flatMap(Function.identity())
        // group indices of the sets by their elements,
        // i.e. accumulate the intersections
        .collect(Collectors.toMap(
                // key - element
                Map.Entry::getKey,
                // value - set of the indices of the sets
                e -> new TreeSet<>(List.of(e.getValue())),
                // accumulate the indices of the sets
                (e1, e2) -> {
                    e1.addAll(e2);
                    return e1;
                }))
        // Stream<Map.Entry<Integer,TreeSet<Integer>>>
        .entrySet().stream()
        // filter out unique elements
        // without intersections
        .filter(e -> e.getValue().size() > 1)
        // intermediate output
        //1=[1, 3]
        //3=[1, 2]
        //6=[1, 3]
        //9=[2, 3]
        //11=[1, 2, 3]
        .peek(System.out::println)
        // group the elements of the
        // sets by their intersections
        .collect(Collectors.toMap(
                // key - set of the indices of the sets
                Map.Entry::getValue,
                // value - set of the intersecting elements
                e -> new TreeSet<>(Set.of(e.getKey())),
                // accumulate the intersecting elements
                (e1, e2) -> {
                    e1.addAll(e2);
                    return e1;
                }))
        // Stream<Map.Entry<TreeSet<Integer>,TreeSet<Integer>>>
        .entrySet().stream()
        // intermediate output
        //[1, 2]=[3]
        //[1, 3]=[1, 6]
        //[2, 3]=[9]
        //[1, 2, 3]=[11]
        .peek(System.out::println)
        // group by the number of intersections
        .collect(Collectors.groupingBy(
                // size of the set of indices
                e -> e.getKey().size(),
                // sort by number of intersections in reverse order
                () -> new TreeMap<>(Comparator.<Integer>reverseOrder()),
                // list of map entries
                Collectors.toList()));

// intermediate output
map.forEach((k, v) -> System.out.println(k + "=" + v));
//3=[[1, 2, 3]=[11]]
//2=[[1, 2]=[3], [1, 3]=[1, 6], [2, 3]=[9]]

// process the lists of intersections, i.e. map entries
map.forEach((key, value) -> value
        // for each entry process the values of other
        // entries with less number of intersections
        .forEach(entry -> map.tailMap(key, false)
                // Stream<List<Map.Entry<TreeSet<Integer>,TreeSet<Integer>>>>
                .values().stream()
                // Stream<Map.Entry<TreeSet<Integer>,TreeSet<Integer>>>
                .flatMap(List::stream)
                // if the intersection set of the current entry contains
                // all intersections from the set of another entry
                .filter(other -> entry.getKey().containsAll(other.getKey()))
                // then add all intersecting elements of
                // the current entry to another entry
                .forEach(other -> other.getValue().addAll(entry.getValue()))));

// final output
map.forEach((k, v) -> v.forEach(entry -> System.out.println(
        "Sets: " + entry.getKey() + " contains values: " + entry.getValue())));

//Sets: [1, 2, 3] contains values: [11]
//Sets: [1, 2] contains values: [3, 11]
//Sets: [1, 3] contains values: [1, 6, 11]
//Sets: [2, 3] contains values: [9, 11]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接