如何生成给定列表的幂集?

Posted

技术标签:

【中文标题】如何生成给定列表的幂集?【英文标题】:How to generate the power-set of a given List? 【发布时间】:2014-01-22 23:48:34 【问题描述】:

我正在尝试生成长度为 N 的给定列表的所有 2^N - 1 种可能组合的集合。该集合会将组合中的元素数量映射到包含特定组合的有序组合列表长度。例如,对于列表:

[A, B, C, D]

我要生成地图:


    1 -> [A, B, C, D]
    2 -> [A, B, A, C, A, D, B, C, B, D, C, D]
    3 -> [A, B, C, A, B, D, A, C, D, B, C, D]
    4 -> [A, B, C, D]

生成的数据库应保持原始顺序(其中[] 表示有序序列(List), 表示无序组(Set)),并尽可能快地运行。

我整天都在为一些递归代码苦苦挣扎(我知道实现应该是递归的),但无法深入了解它。

有没有我可以使用的参考/此类算法的现成实现?

【问题讨论】:

我不确定递归是否特别有助于解决这个问题。试着反复思考一下。 如果您反复进行,请注意您可以同时生成大小i 和大小N-i 列表。考虑将列表划分为两个子集,并将每个子集添加到您的结果列表之一。 这是一个有趣的方法,我会研究一下。 我投票结束这个问题,因为它正在请求场外资源。 【参考方案1】:

您要查找的本质上是power set(可能减去空集)。 Guava 实际上有一个方法:Sets.powerSet()。想自己写的可以查看source of the Sets class看看方法是怎么实现的;您可能需要修改它以返回 List 而不是 Set,因为您想保留顺序,尽管此更改不应过于激烈。一旦你有了权力集,迭代它并构建你想要的地图应该是微不足道的。

【讨论】:

【参考方案2】:

您要问的是生成集合的所有可能子集。您可以将其视为遍历所有可能的大小为 N(列表的大小)的二进制数组:

000000...000
000000...001
000000...010
000000...011
etc.

这是为什么呢?答案很简单:1 表示元素存在于子集中,0 表示不存在。

那么,基本算法就很明显了:

s = [A, B, C, D]

for i=0 to 2^N-1:
   b = convert_number_to_bin_array(i)
   ss = calculate_subset_based_on_bin_array(s, b)
   print ss

calculate_subset_based_on_bin_arraybs 上迭代,并从 s[current] where b[current] = 1 中选择元素。

以上将打印出所有现有的子集。您可以调整此算法以获得您在问题中要求的地图。

【讨论】:

你是对的,但我认为你建议的实现迫使我大约 O((N^2 - 1) * N * N),我想我可以得到一个更有效的实现。 我的意思是,如果您知道将 1 放在二进制数组中的哪个位置 - 您可以将相应的元素添加到当前列表中。 @Elist 当然你可以改进我提供的算法,这就是为什么它被称为 basic :-)【参考方案3】:
static Map<Integer, List<LinkedList<Integer>>> powerset = new HashMap<>();

public static void main(String[] args) throws IOException 
    powerset(Arrays.asList(1, 2, 3));
    for (Integer key : powerset.keySet()) 
        System.out.print(key + " -> ");
        System.out.println(Arrays.toString(powerset.get(key).toArray()));
    


static void powerset(List<Integer> src) 
    powerset(new LinkedList<>(), src);


private static void powerset(LinkedList<Integer> prefix, List<Integer> src) 
    if (src.size() > 0) 
        prefix = new LinkedList<>(prefix); //create a copy to not modify the orig
        src = new LinkedList<>(src); //copy
        Integer curr = src.remove(0);
        collectResult(prefix, curr);
        powerset(prefix, src);
        prefix.add(curr);
        powerset(prefix, src);
    


private static void collectResult(LinkedList<Integer> prefix, Integer curr) 
    prefix = new LinkedList<>(prefix); //copy
    prefix.add(curr);
    List<LinkedList<Integer>> addTo;
    if (powerset.get(prefix.size()) == null) 
        List<LinkedList<Integer>> newList = new LinkedList<>();
        addTo = newList;
     else 
        addTo = powerset.get(prefix.size());
    
    addTo.add(prefix);
    powerset.put(prefix.size(), addTo);

输出

1 -> [[1], [2], [3]]
2 -> [[2, 3], [1, 2], [1, 3]]
3 -> [[1, 2, 3]]

【讨论】:

【参考方案4】:

这是我测试过的代码,用于从给定数组中生成所有可能的组合:

enter code here
import java.util.Arrays;

public class PasswordGen 
static String[] options =  "a", "b", "c", "d" ;
static String[] places = new String[options.length];
static int count;

public static void main(String[] args) 
    // Starting with initial position of a i.e. 0
    sequence(0, places.clone());


private static void sequence(int level, String[] holder) 
    if (level >= options.length) 
        // combination complete
        System.out.println("" + (++count) + " Combination "
                + Arrays.toString(holder));
        return;
    

    String val = options[level];
    String[] inrHolder = null;
    for (int c = 0; c < holder.length; c++) 
        inrHolder = holder.clone();
        if (inrHolder[c] == null) 
            inrHolder[c] = val;
            sequence(level + 1, inrHolder.clone());
        
    
    return;


【讨论】:

我一直在寻找列表组合的所有可能集合,例如,这意味着[a, b][b, a] 不会同时出现在输出中(因为a 在@ 之前987654325@ 在输入中,[b, a] 不保持原来的顺序)。 你是对的 - 此代码生成所有组合 - 但 OP 希望生成数组/列表的幂集并将其插入到将按每个子集的大小排序的 Map 中。 OP 误用了“组合”一词 - 我修正了问题的标题以指出我们正在寻找的是功率集。 +1 可以很好地实现组合:)【参考方案5】:

有多种方法可以解决这个问题,但恕我直言,最简单的方法是使用递归。下面我提供了 iterativerecursive 方法来解决生成集合的所有组合的问题。这两种方法的总体思路是生成属于您要选择的元素的索引集。

对于递归方法,您需要跟踪三项信息:一个布尔数组,指示是否选择了一个项目,您在项目列表中的位置以及一个变量 r,用于跟踪项目的数量剩余要选择的元素。然后只要 r != 0 (意味着你仍然需要选择 r 个元素来完成这个组合)你回溯选择一个你还没有选择的元素并在数组中前进。

下面显示的代码取自我的github repo。

/**
 * Here we present two methods (recursive and iterative) of generating all
 * the combinations of a set by choosing only r of n elements.
 * 
 * Time Complexity: O( n choose r )
 *
 * @author William Fiset, Micah Stairs
 **/

public class Combinations 

  // This method finds all the combinations of size r in a set
  public static void combinationsChooseR(int[] set, int r) 

    if (r < 0) return;
    if (set == null) return;

    boolean [] used = new boolean[set.length];
    combinations(set, r, 0, used);

  

  // To find all the combinations of size r we need to recurse until we have
  // selected r elements (aka r = 0), otherwise if r != 0 then we need to select
  // an element which is found after the position of our last selected element
  private static void combinations(int[] set, int r, int at, boolean[] used) 

    final int N = set.length;

    // If there are more elements left to select than what are available
    // This is a short circuiting optimization we can take advantage of 
    int elementsLeftToPick = N - at;
    if (elementsLeftToPick < r) return;

    // We selected 'r' elements so we found a valid subset!
    if (r == 0) 

      System.out.print(" ");
      for (int i = 0; i < N; i++)
        if (used[i])
          System.out.print(set[i] + " ");
      System.out.println("");

     else 

      for (int i = at; i < N; i++) 

        // Try including this element
        used[i] = true;
        combinations(set, r - 1, i + 1, used);

        // Backtrack and try the instance where we did not include this element
        used[i] = false;

      

    

  

  // Use this method in combination with a do while loop to generate all the 
  // combinations of a set choose r in a iterative fashion. This method returns
  // false once the last combination has been generated.
  // NOTE: Originally the selection needs to be initialized to 0,1,2,3 ... r-1
  public static boolean nextCombination(int[] selection, int N, int r) 
    if (r > N) throw new IllegalArgumentException("r must be <= N");
    int i = r - 1;
    while (selection[i] == N - r + i)
      if (--i < 0) return false;
    selection[i]++;
    for (int j = i + 1; j < r; j++) selection[j] = selection[i] + j - i;
    return true;
  

  public static void main(String[] args) 

    // Recursive approach
    int R = 3;
    int[] set = 1,2,3,4,5;
    combinationsChooseR(set, R);
    // prints:
    //  1 2 3 
    //  1 2 4 
    //  1 2 5 
    //  1 3 4 
    //  1 3 5 
    //  1 4 5 
    //  2 3 4 
    //  2 3 5 
    //  2 4 5 
    //  3 4 5       

    // Suppose we want to select all combinations of colors where R = 3
    String[] colors = "red", "purple", "green", "yellow", "blue", "pink";
    R = 3;

    // Initialize the selection to be 0, 1, ... , R-1
    int[] selection =  0, 1, 2 ;
    do 

      // Print each combination
      for (int index : selection)
        System.out.print(colors[index] + " ");
      System.out.println();

     while( nextCombination(selection, colors.length, R) );
    // prints:
    // red purple green 
    // red purple yellow 
    // red purple blue 
    // red purple pink 
    // red green yellow 
    // red green blue 
    // red green pink 
    // red yellow blue 
    // red yellow pink 
    // red blue pink 
    // purple green yellow 
    // purple green blue 
    // purple green pink 
    // purple yellow blue 
    // purple yellow pink 
    // purple blue pink 
    // green yellow blue 
    // green yellow pink 
    // green blue pink 
    // yellow blue pink    

  


【讨论】:

【参考方案6】:
public static List<String> getCombinationsLists(List<String> elements)

    
    //return list with empty String
    if(elements.size() == 0)
        List<String> allLists = new ArrayList<String>();
        allLists.add("");
        return allLists ;
    

    String first_ele = elements.remove(0);
    List<String> rest = getCombinationsLists(elements);
    int restsize = rest.size();
    //Mapping the first_ele with each of the rest of the elements.
    for (int i = 0; i < restsize; i++) 
        String ele = first_ele + rest.get(i);
        rest.add(ele);
    
   
    return   rest;

这个Power set是SICP“Structure and Interpretation of Computer Programming”一书中的练习之一。每个程序员都应该阅读它。

【讨论】:

【参考方案7】:

我测试了 Elist 提出的代码,发现错误。

这是一个建议的更正:(在函数getPermutation()的最后一个else中,我做了两处更改)

public class OrderedPowerSet<E> 
private ArrayList<E> inputList;
public int N;
private Map<Integer, List<Set<E>>> map = 
        new HashMap<Integer, List<Set<E>>>();

public OrderedPowerSet(ArrayList<E> list) 
    inputList = list;
    N = list.size();


public List<Set<E>> getPermutationsList(int elementCount) 
    if (elementCount < 1 || elementCount > N) 
        throw new IndexOutOfBoundsException(
                "Can only generate permutations for a count between 1 to " + N);
    
    if (map.containsKey(elementCount)) 
        return map.get(elementCount);
    

    ArrayList<Set<E>> list = new ArrayList<Set<E>>();

    if (elementCount == N) 
        list.add(new HashSet<E>(inputList));
     else if (elementCount == 1) 
        for (int i = 0 ; i < N ; i++) 
            Set<E> set = new HashSet<E>();
            set.add(inputList.get(i));
            list.add(set);
        
     else 
        for (int i = 0 ; i < N-elementCount ; i++) 
            @SuppressWarnings("unchecked")
            ArrayList<E> subList = (ArrayList<E>)inputList.clone(); // one change
            subList.remove(0);
            OrderedPowerSet<E> subPowerSet = 
                    new OrderedPowerSet<E>(subList);
            for (Set<E> s : subPowerSet.getPermutationsList(elementCount-1)) 
                Set<E> set = new HashSet<E>();
                set.add(inputList.get(i));
                set.addAll(s);
                list.add(set); // second change
            

        
    

    map.put(elementCount, list);

    return map.get(elementCount);

【讨论】:

【参考方案8】:

OP 的解决方案从问题变成了答案:

感谢之前的回答,我想出了以下实现:

public class OrderedPowerSet<E> 
    private static final int ELEMENT_LIMIT = 12;
    private List<E> inputList;
    public int N;
    private Map<Integer, List<LinkedHashSet<E>>> map = 
            new HashMap<Integer, List<LinkedHashSet<E>>>();

    public OrderedPowerSet(List<E> list) 
        inputList = list;
        N = list.size();
        if (N > ELEMENT_LIMIT) 
            throw new RuntimeException(
                    "List with more then " + ELEMENT_LIMIT + " elements is too long...");
        
    

    public List<LinkedHashSet<E>> getPermutationsList(int elementCount) 
        if (elementCount < 1 || elementCount > N) 
            throw new IndexOutOfBoundsException(
                    "Can only generate permutations for a count between 1 to " + N);
        
        if (map.containsKey(elementCount)) 
            return map.get(elementCount);
        

        ArrayList<LinkedHashSet<E>> list = new ArrayList<LinkedHashSet<E>>();

        if (elementCount == N) 
            list.add(new LinkedHashSet<E>(inputList));
         else if (elementCount == 1) 
            for (int i = 0 ; i < N ; i++) 
                LinkedHashSet<E> set = new LinkedHashSet<E>();
                set.add(inputList.get(i));
                list.add(set);
            
         else 
            list = new ArrayList<LinkedHashSet<E>>();
            for (int i = 0 ; i <= N - elementCount ; i++) 
                @SuppressWarnings("unchecked")
                ArrayList<E> subList = (ArrayList<E>)((ArrayList<E>)inputList).clone();
                for (int j = i ; j >= 0 ; j--) 
                    subList.remove(j);
                
                OrderedPowerSet<E> subPowerSet = 
                        new OrderedPowerSet<E>(subList);

                List<LinkedHashSet<E>> pList = 
                        subPowerSet.getPermutationsList(elementCount-1);
                for (LinkedHashSet<E> s : pList) 
                    LinkedHashSet<E> set = new LinkedHashSet<E>();
                    set.add(inputList.get(i));
                    set.addAll(s);
                    list.add(set);
                               
            
        

        map.put(elementCount, list);

        return map.get(elementCount);
    

【讨论】:

以上是关于如何生成给定列表的幂集?的主要内容,如果未能解决你的问题,请参考以下文章

一组python的幂集和笛卡尔积

递归幂集函数的时间复杂度

11.幂集计算[回溯递归]<一>

两种求幂集的方法

2的幂?代码有啥问题

如何检查列表中的项目是不是是python中任意数字的幂?