如何在Java中递归地从N元素集中生成所有k元素子集

Posted

技术标签:

【中文标题】如何在Java中递归地从N元素集中生成所有k元素子集【英文标题】:How to Generate all k-elements subsets from an N-elements set recursively in Java 【发布时间】:2011-05-05 03:17:53 【问题描述】:

所以我遇到了这个问题,试图从给定的 N 元素集中找到所有 k 元素子集。我知道使用公式 C(n,k)=C(n-1, k-1)+C(n-1, k) 的 k 子集的总数是多少,我也知道怎么做以迭代的方式,但是当我尝试考虑递归解决方案时,我陷入了困境。谁能给我一个提示? 谢谢!

【问题讨论】:

你应该阅读格雷码。 你会如何迭代,迭代有什么问题? @MB,因为这是作业要求的,我怀疑。 @MB,在迭代中没有奇迹发生。简单的话是蹩脚的。 @Woot4Moo:我提供了一些... 【参考方案1】:

对于集合的每个元素,取那个元素,然后依次添加到剩余 N-1 个元素集合的所有 (k-1) 个子集。

“那是一个黑暗而暴风雨的夜晚,船长说……”

【讨论】:

我真的需要知道如何将其转换为递归方法,因为如前所述,我有一个涉及嵌套循环的迭代解决方案,但不知道如何将其转换为递归方法。 那是递归的方法!它说产生 N 集的 k 子集的问题与获取 N 的每个元素,然后计算剩余的 N-1 大小集的 k-1 集相同。当 K 达到 1 时,计算出 hte 子集是微不足道的。 对不起,你是对的!我想评论作为迭代解决方案的第二个答案,但它消失了,所以我最终在错误的地方发表评论。 这有重复。没有这些就好了。【参考方案2】:

更好

这对于k=0 的情况是错误的,因为我认为它会返回一个包含空集的集合,这不太正确。反正。这里还有一个迭代,如果目标是纯递归的,你可以用递归替换它。这是对wikipedia: powerset 给出的算法的相当简单的修改。我将把修复角落案例 (k=0) 的工作留给读者。

这不是正确的尾递归,在大多数 JVM 中并不重要。 (我猜 IBM JVM 是这样做的……)

class RecursivePowerKSet
  
  static public <E> Set<Set<E>> computeKPowerSet(final Set<E> source, final int k)
  
    if (k==0 || source.size() < k) 
      Set<Set<E>> set = new HashSet<Set<E>>();
      set.add(Collections.EMPTY_SET);
      return set;
    

    if (source.size() == k) 
      Set<Set<E>> set = new HashSet<Set<E>>();
      set.add(source);
      return set;
    

    Set<Set<E>> toReturn = new HashSet<Set<E>>();

    // distinguish an element
    for(E element : source) 
      // compute source - element
      Set<E> relativeComplement = new HashSet<E>(source);
      relativeComplement.remove(element);

      // add the powerset of the complement
      Set<Set<E>> completementPowerSet = computeKPowerSet(relativeComplement,k-1);
      toReturn.addAll(withElement(completementPowerSet,element));
    

    return toReturn;
  

  /** Given a set of sets S_i and element k, return the set of sets S_i U k */ 
  static private <E> Set<Set<E>> withElement(final Set<Set<E>> source, E element)
  

    Set<Set<E>> toReturn = new HashSet<Set<E>>();
    for (Set<E> setElement : source) 
      Set<E> withElementSet = new HashSet<E>(setElement);
      withElementSet.add(element);
      toReturn.add(withElementSet);
    

    return toReturn;
  

  public static void main(String[] args)
  
    Set<String> source = new HashSet<String>();
    source.add("one");
    source.add("two");
    source.add("three");
    source.add("four");
    source.add("five");

    Set<Set<String>> powerset = computeKPowerSet(source,3);

    for (Set<String> set : powerset) 
      for (String item : set) 
        System.out.print(item+" ");
      
      System.out.println();
       
  

仅限电源组 这可能不会完全到达那里,也不是很优雅,但它会递归计算完整的 powerset,然后(迭代地)修剪它的大小。

class RecursivePowerSet



  static public <E> Set<Set<E>> computeConstrainedSets(final Set<Set<E>> source, final SizeConstraint<Set<E>> constraint)
  
    Set<Set<E>> constrained = new HashSet<Set<E>>();
    for (Set<E> candidate : source) 
      if (constraint.meetsConstraint(candidate)) 
        constrained.add(candidate);
      
    
    return constrained;
  

  static public <E> Set<Set<E>> computePowerSet(final Set<E> source)
  

    if (source.isEmpty()) 
      Set<Set<E>> setOfEmptySet = new HashSet<Set<E>>();
      setOfEmptySet.add(Collections.EMPTY_SET);
      return setOfEmptySet;
    


    Set<Set<E>> toReturn = new HashSet<Set<E>>();

    // distinguish an element
    E element = source.iterator().next();

    // compute source - element
    Set<E> relativeComplement = new HashSet<E>(source);
    relativeComplement.remove(element);

    // add the powerset of the complement
    Set<Set<E>> completementPowerSet = computePowerSet(relativeComplement);
    toReturn.addAll(completementPowerSet);
    toReturn.addAll(withElement(completementPowerSet,element));

    return toReturn;
  

  static private <E> Set<Set<E>> withElement(final Set<Set<E>> source, E element)
  

    Set<Set<E>> toReturn = new HashSet<Set<E>>();
    for (Set<E> setElement : source) 
      Set<E> withElementSet = new HashSet<E>(setElement);
      withElementSet.add(element);
      toReturn.add(withElementSet);
    

    return toReturn;
  

  public static void main(String[] args)
  
    Set<String> source = new HashSet<String>();
    source.add("one");
    source.add("two");
    source.add("three");
    source.add("four");
    source.add("five");

    SizeConstraint<Set<String>> constraint = new SizeConstraint<Set<String>>(3);

    Set<Set<String>> powerset = computePowerSet(source);
    Set<Set<String>> constrained = computeConstrainedSets(powerset, constraint);
    for (Set<String> set : constrained) 
      for (String item : set) 
        System.out.print(item+" ");
      
      System.out.println();
    

  

  static class SizeConstraint<V extends Set> 

    final int size;
    public SizeConstraint(final int size)
    
     this.size = size; 
    

    public boolean meetsConstraint(V set)
    
      return set.size() == size;
    
  


【讨论】:

@user497302:为什么不呢?它递归地计算所有 k 子集……编译它,它就可以工作了。【参考方案3】:

这是一些伪代码。您可以通过在执行过程中存储每个调用的值以及在递归调用之前检查调用值是否已经存在来减少相同的递归调用。

以下算法将包含除空集之外的所有子集。

list * subsets(string s, list * v)
    if(s.length() == 1)
        list.add(s);    
        return v;
    
    else
    
        list * temp = subsets(s[1 to length-1], v);     
        int length = temp->size();

        for(int i=0;i<length;i++)
            temp.add(s[0]+temp[i]);
        

        list.add(s[0]);
        return temp;
    

【讨论】:

以上是关于如何在Java中递归地从N元素集中生成所有k元素子集的主要内容,如果未能解决你的问题,请参考以下文章

Java 快速排序算法的简单说明及实现

分治与递归-线性时间选择

在CSS中递归选择所有子元素

如何在两个排序数组的并集中找到第 k 个最小的元素?

PHP实现全排列(递归算法)

如何生成所有长度为 n 且设置了 k 位的二进制模式? (使用递归)