n个集合的所有可能组合

Posted

技术标签:

【中文标题】n个集合的所有可能组合【英文标题】:All possible combination of n sets 【发布时间】:2013-05-09 03:02:26 【问题描述】:

我有n 集。每个 Set 有不同数量的元素。我想编写一个算法,它可以为我提供集合中所有可能的组合。例如,假设我们有:

S1=1,2, S2=A,B,C, S3=$,%,£,!

组合应该是这样的

C1=1,A,$
C2=1,A,%
...

...等等。可能的组合数量为2*3*4 = 24

请帮我用Java写这个算法。

【问题讨论】:

【参考方案1】:

递归是你的朋友:

public class PrintSetComb 
  public static void main(String[] args) 
    String[] set1 = "1", "2";
    String[] set2 = "A", "B", "C";
    String[] set3 = "$", "%", "£", "!";
    String[][] sets = set1, set2, set3;

    printCombinations(sets, 0, "");
  

  private static void printCombinations(String[][] sets, int n, String prefix) 
    if (n >= sets.length) 
      System.out.println("" + prefix.substring(0, prefix.length() - 1) + "");
      return;
    
    for (String s : sets[n]) 
      printCombinations(sets, n + 1, prefix + s + ",");
    
  

回答 OP 关于将其推广到对象集的问题:

public class PrintSetComb 
  public static void main(String[] args) 
    Integer[] set1 = 1, 2;
    Float[] set2 = 2.0F, 1.3F, 2.8F;
    String[] set3 = "$", "%", "£", "!";
    Object[][] sets = set1, set2, set3;

    printCombinations(sets, 0, new Object[0]);
  

  private static void printCombinations(Object[][] sets, int n, Object[] prefix) 
    if (n >= sets.length) 
      String outp = "";
      for (Object o : prefix) 
        outp = outp + o.toString() + ",";
      
      System.out.println(outp.substring(0, outp.length() - 1) + "");
      return;
    
    for (Object o : sets[n]) 
      Object[] newPrefix = Arrays.copyOfRange(prefix, 0, prefix.length + 1);
      newPrefix[newPrefix.length - 1] = o;
      printCombinations(sets, n + 1, newPrefix);
    
  

最后是一个迭代变体。它基于增加一个计数器数组,当计数器达到集合的大小时,计数器会包装并携带:

public class PrintSetCombIterative 
  public static void main(String[] args) 
    String[] set1 = "1", "2";
    String[] set2 = "A", "B", "C";
    String[] set3 = "$", "%", "£", "!";
    Object[][] sets = set1, set2, set3;

    printCombinations(sets);
  

  private static void printCombinations(Object[][] sets) 
    int[] counters = new int[sets.length];

    do 
      System.out.println(getCombinationString(counters, sets));
     while (increment(counters, sets));
  

  private static boolean increment(int[] counters, Object[][] sets) 
    for (int i = counters.length - 1; i >= 0; i--) 
      if (counters[i] < sets[i].length - 1) 
        counters[i]++;
        return true;
       else 
        counters[i] = 0;
      
    
    return false;
  

  private static String getCombinationString(int[] counters, Object[][] sets) 
    String combo = "";
    for (int i = 0; i < counters.length; i++) 
      combo = combo + sets[i][counters[i]] + ",";
    
    return combo.substring(0, combo.length() - 1) + "";
  

【讨论】:

【参考方案2】:

如果有人想要矩阵而不是打印,我稍微修改了代码:

public class TestSetCombinations2 
  public static void main(String[] args) 
    Double[] set1 = 2.0, 3.0;
    Double[] set2 = 4.0, 2.0, 1.0;
    Double[] set3 = 3.0, 2.0, 1.0, 5.0;
    Double[] set4 = 1.0, 1.0;
    Object[][] sets = set1, set2, set3, set4;

    Object[][] combinations = getCombinations(sets);

    for (int i = 0; i < combinations.length; i++) 
      for (int j = 0; j < combinations[0].length; j++) 
        System.out.print(combinations[i][j] + " ");
      
      System.out.println();
    
  

  private static Object[][] getCombinations(Object[][] sets) 
    int[] counters = new int[sets.length];
    int count = 1;
    int count2 = 0;

    for (int i = 0; i < sets.length; i++) 
      count *= sets[i].length;
    

    Object[][] combinations = new Object[count][sets.length];

    do 
      combinations[count2++] = getCombinationString(counters, sets);
     while (increment(counters, sets));

    return combinations;
  

  private static Object[] getCombinationString(int[] counters, Object[][] sets) 
    Object[] o = new Object[counters.length];
    for (int i = 0; i < counters.length; i++) 
      o[i] = sets[i][counters[i]];
    
    return o;
  

  private static boolean increment(int[] counters, Object[][] sets) 
    for (int i = counters.length - 1; i >= 0; i--) 
      if (counters[i] < sets[i].length - 1) 
        counters[i]++;
        return true;
       else 
        counters[i] = 0;
      
    
    return false;
  

【讨论】:

【参考方案3】:

krilid 的出色解决方案,我对其进行了一些修改以返回所有组合的列表。

@Test
public void testMap() throws Exception 
    char[] one = new char[]'a', 'b', 'c';
    char[] two = new char[]'d', 'e', 'f';
    char[] three = new char[]'g', 'h', 'i', 'j';
    char[][] sets = new char[][]one, two, three;
    List<List<Character>> collector = new ArrayList<>();
    ArrayList<Character> combo = new ArrayList<>();
    combinations(collector, sets, 0, combo);
    System.out.println(collector);


private void combinations(List<List<Character>> collector,
                          char[][] sets, int n,
                          ArrayList<Character> combo) 
    if (n == sets.length) 
        collector.add(new ArrayList<>(combo));
        return;
    
    for (char c : sets[n]) 
        combo.add(c);
        combinations(collector, sets, n + 1, combo);
        combo.remove(combo.size() - 1);
    

【讨论】:

【参考方案4】:

map 和 reduce 方法

您可以创建一个通用方法来获取不同类型和数量的集合及其元素的笛卡尔积

Try it online!

public static void main(String[] args) 
  Set<Integer> a = Set.of(1, 2);
  List<String> b = List.of("A", "B", "C");
  Set<Character> c = Set.of('$', '%', '£', '!');

  Set<Set<Object>> cpSet = cartesianProduct(HashSet::new, a, b, c);
  List<List<Object>> cpList = cartesianProduct(ArrayList::new, a, b, c);

  // output, order may vary
  System.out.println(cpSet);
  System.out.println(cpList);

/**
 * @param nCol the supplier of the output collection
 * @param cols the input array of collections
 * @param <R>  the type of the return collection
 * @return the cartesian product of the multiple collections
 */
@SuppressWarnings("unchecked")
public static <R extends Collection<?>> R cartesianProduct(
      Supplier nCol, Collection<?>... cols) 
  // check if supplier is not null
  if (nCol == null) return null;
  return (R) Arrays.stream(cols)
      // non-null and non-empty collections
      .filter(col -> col != null && col.size() > 0)
      // represent each element of a collection as a singleton collection
      .map(col -> (Collection<Collection<?>>) col.stream()
          .map(e -> Stream.of(e).collect(Collectors.toCollection(nCol)))
          .collect(Collectors.toCollection(nCol)))
      // summation of pairs of inner collections
      .reduce((col1, col2) -> (Collection<Collection<?>>) col1.stream()
          // combinations of inner collections
          .flatMap(inner1 -> col2.stream()
              // concatenate into a single collection
              .map(inner2 -> Stream.of(inner1, inner2)
                  .flatMap(Collection::stream)
                  .collect(Collectors.toCollection(nCol))))
          // collection of combinations
          .collect(Collectors.toCollection(nCol)))
      // otherwise an empty collection
      .orElse((Collection<Collection<?>>) nCol.get());

输出(裁剪,顺序可能不同):

[[1, A, !], [1, !, B], [A, !, 2], [1, A, £], [1, !, C]...
[[1, A, $], [1, A, %], [1, A, £], [1, A, !], [1, B, $]...

另见:Finding cartesian product in Java

【讨论】:

以上是关于n个集合的所有可能组合的主要内容,如果未能解决你的问题,请参考以下文章

java题目好难啊?求大侠解答,谢谢、、 设计算法求解从集合1...n中选取k(k<=n)个元素的所有组合。

在C中创建n个项目的k和m个组合的所有可能子集[重复]

第 N 个组合

第 N 个组合

BZOJ2339[HNOI2011]卡农 组合数+容斥

从多个集合中创建所有可能的组合