【问题标题】:Generating power set recursively without any loops没有任何循环的递归发电组
【发布时间】:2013-03-19 11:30:36
【问题描述】:

如何编写一个递归方法 PowerSet(String input) 打印出传递给它的字符串的所有可能组合?

例如:PowerSet("abc") 会打印出 abc,ab,ac,bc,a,b,c

我见过一些带有循环的递归解决方案,但在这种情况下不允许使用循环。

有什么想法吗?

编辑:所需方法只有一个参数,即字符串输入。

【问题讨论】:

  • 这种情况?哪种情况?
  • 我认为有一些算法可以解决这个问题,以防你使用谷歌找到一个。
  • 几乎每个循环都可以用递归函数代替。
  • @R.J.我的意思是在这种情况下,不允许循环。这就是问题的要求。 @Matten 我发现了一些但大多数都不合适,因为它们有超过 1 个参数。
  • 您实际上拥有多个参数:String.getBytes();

标签: java algorithm recursion


【解决方案1】:

abcd 的幂集是abcabdacd 的幂集的并集(加上集abcd 本身*)。

 P(`abcd`) = {`abcd`} + P(`abc`) + P(`abd`) + P(`acd`) + P(`bcd`)

* 注意 空集,它是 P(abcd) 的成员,也是 P(abc)、P(abd)、...的成员,所以等价上述成立。

递归地,P(abc) = {abc} + P(ab) + P(ac),以此类推

第一种方法,在伪代码中,可能是:

powerset(string) {
  add string to set;
  for each char in string {
   let substring = string excluding char,
   add powerset(substring) to set
  }
  return set;      
}

当字符串为空时递归结束(因为它永远不会进入循环)。

如果您真的想要 no 循环,则必须将该循环转换为另一个递归。 现在我们要从abc生成abaccb

powerset(string) {
  add string to set;
  add powerset2(string,0) to set;
  return set
}

powerset2(string,pos) {
  if pos<length(string) then
    let substring = (string excluding the char at pos)
    add powerset(substring) to set
    add powerset2(string,pos+1) to set
  else 
    add "" to set
  endif
  return set
}

另一种方法实现一个递归函数P,它要么从参数中删除第一个字符,要么不删除。 (这里+表示集合并集,.表示连接,λ是空字符串)

P(abcd) = P(bcd) + a.P(bcd)
P(bcd)  = P(cd)  + b.P(cd)
P(cd)   = P(d)   + c.P(d)
P(d)    = λ+d //particular case

然后

P(d)    = λ+d
R(cd)   = P(d)  + c.P(d)  = λ + d + c.(λ+d) = λ + d + c + cd
R(bcd)  = P(cd) + b.P(cd) = λ + d + c + cd + b.(λ + d + c + cd)
                          = λ + d + c + cd + b + bd + bc + bcd
P(abcd) =  λ +  d +  c +  cd +  b +  bd +  bc +  bcd 
        + aλ + ad + ac + acd + ab + abd + abc + abcd 

如果允许循环,则P 将失去电源设置功能。否则,我们将需要一个单参数无循环函数来将给定字符连接到给定字符串集(这显然是两个东西)。

可以通过使用String.replace 进行一些调整(如果需要String 结果,或者将Set 替换为List (这样“附加”参数实际上是列表中的第一个元素) )。

【讨论】:

  • 太棒了,我确实想到了你的伪代码中的算法。但是我在执行这个任务时遇到了困难:让 substring = string 不包括 char。 API 中是否有任何内置函数可以做到这一点?
  • s.substring(0,pos) 将返回从0pos-1 的子字符串,s.substring(pos) 将返回从pos 到字符串末尾的子字符串。
  • 谢谢。我知道了。无论如何,我很迂腐,因为问题只提到了一个参数。你知道如何实现这个方法,只有一个参数,即字符串输入吗?
  • 我提出了另一种在递归方面更好的方法,但仍然需要循环。这篇文章似乎包含答案,但我无法下载它dl.acm.org/citation.cfm?id=1151793
  • @ArtemStepanenko 谢谢!我修正了错字。关于空集,它不仅是P(abcd)的成员,也是P(abc)等的成员。
【解决方案2】:

这也可以解决问题:

var powerset = function(arr, prefix, subsets) {
  subsets = subsets || [];
  prefix = prefix || [];
  if (arr.length) {
    powerset(arr.slice(1), prefix.concat(arr[0]), subsets);
    powerset(arr.slice(1), prefix, subsets);
  } else {
    subsets.push(prefix);
  }
  return subsets;
};

powerset('abc');

【讨论】:

    【解决方案3】:

    如果你没有循环,用递归模拟一个循环,使用迭代器这真的很简单。

        public final Set<Set<Integer>> powerSet(Set<Integer> set) {
            Set<Set<Integer>> powerSet = new HashSet<>();
            powerSet(set, powerSet, set.iterator());
            return powerSet;
        }
        public final void powerSet(Set<Integer> set, Set<Set<Integer>> powerSet, Iterator<Integer> iterator) {
            if(iterator.hasNext()) {
                Integer exlude = iterator.next();
                Set<Integer> powThis = new HashSet<Integer>();
                powThis.addAll(set);
                powThis.remove(exlude);
                powerSet.add(powThis);
                powerSet(powThis, powerSet, powThis.iterator());
                powerSet(set, powerSet, iterator);
            }
        }
    //usage
            Set<Integer> set = new HashSet<>();
            set.add(1);
            set.add(2);
            set.add(3);
            set.add(4);
            log.error(powerSet(set).toString());
    

    【讨论】:

      【解决方案4】:

      João Silva 提出的generic solution 的递归版本:

      public static <T> Set<Set<T>> powerSet2(Set<T> originalSet) {
          Set<Set<T>> sets = new HashSet<Set<T>>();
          if (originalSet.isEmpty()) {
              sets.add(new HashSet<T>());
              return sets;
          }
          List<T> list = new ArrayList<T>(originalSet);
          T head = list.get(0);
          Set<T> rest = new HashSet<T>(list.subList(1, list.size()));
          addSets(sets, powerSet(rest), head);
          return  sets;
      }
      
      private static <T> void addSets(Set<Set<T>> sets, Set<Set<T>> setsToAdd, T head) {
          Iterator<Set<T>> iterator = setsToAdd.iterator();
          if (iterator.hasNext()) {
              Set<T> set = iterator.next();
              iterator.remove();
              Set<T> newSet = new HashSet<T>();
              newSet.add(head);
              newSet.addAll(set);
              sets.add(newSet);
              sets.add(set);
              addSets(sets, setsToAdd, head);
          }
      }
      

      我提取递归addSets方法来改造原来的for循环:

      for (Set<T> set : powerSet(rest)) {
          Set<T> newSet = new HashSet<T>();
          newSet.add(head);
          newSet.addAll(set);
          sets.add(newSet);
          sets.add(set);
      }
      

      【讨论】:

        【解决方案5】:
        void powerSet(int * ar, int *temp, int n, int level,int index)
        {
            if(index==n) return;
        
            int i,j;
            for(i=index;i<n;i++)
            {
            temp[level]=ar[i];
        
            for(j=0;j<=level;j++)
            printf("%d ",temp[j]);
            printf("   - - -  t\n");
        
            powerSet(ar, temp, n, level+1,i+1);
            }
        }
        
        int main()
        {
            int price[] = {1,2,3,7};
            int temp[4] ={0};
        
            int n = sizeof(price)/sizeof(price[0]);
        
            powerSet(price, temp, n, 0,0);
        
        
            return 0;
        }
        

        【讨论】:

          【解决方案6】:

          简单的解决方案但时间复杂度较差(2^n)如下(记住一件事,一旦我们必须避免(即0)和一旦我们必须采取它(即1):

          public HashSet<int[]> powerSet(int n) {
              return calcPowerSet(n-1, new HashSet<int[]>(), new int[n]);
          }
          
          private HashSet<int[]> calcPowerSet(int n, HashSet<int[]> result, int []set) {
              if(n < 0) {
                  result.add(set.clone());
                  return null;
              }
              else {
                  set[n] = 0;
                  calcPowerSet(n-1, result, set);
                  set[n] = 1;
                  calcPowerSet(n-1, result, set);
                  return result;
              }
          }
          

          【讨论】:

          • 因为有 2^n 个可能的子集,所以你无法获得比 2^n 更好的复杂度。
          【解决方案7】:

          只是为了好玩,这个版本可以对存储在LinkedList 中的任何集合进行 powersets(以便于删除 head 元素)。 Java 8 流完成了功能部分:

          static <T> LinkedList<LinkedList<T>> powerset(LinkedList<T> elements) {
            if (elements.isEmpty()) 
              return copyWithAddedElement(new LinkedList<>(), new LinkedList<>());
            T first = elements.pop();
            LinkedList<LinkedList<T>> powersetOfRest = powerset(elements);
            return Stream.concat(
                powersetOfRest.stream(), 
                powersetOfRest.stream().map(list -> copyWithAddedElement(list, first)))
                    .collect(Collectors.toCollection(LinkedList::new));
          }
          
          static <T> LinkedList<T> copyWithAddedElement(LinkedList<T> list, T elt) {
            list = new LinkedList<>(list);
            list.push(elt);
            return list;
          }
          

          这是受到以下 Common Lisp 的启发,这表明正确的语言可以让事情变得更简单:

          (defun powerset (set)
            (cond ((null set) '(()))
                  (t (let ((powerset-of-rest (powerset (cdr set))))
                    (append powerset-of-rest
                            (mapcar #'(lambda (x) (cons (car set) x)) 
                                    powerset-of-rest))))))
          

          【讨论】:

          • 本着同样的精神,Haskell 中的一个更简单的解决方案:powerSet = filterM (\_ -&gt; [True, False])
          【解决方案8】:

          根据here 的信息,这里是 C# 中的解决方案。

          注意:主函数中的循环只是将结果打印到控制台值中。 PowerSet 方法中没有使用循环。

              public static void Main(string[] args)
              {
          
                  string input = "abbcdd";
          
          
                  Dictionary < string, string> resultSet = new Dictionary<string, string>();
          
                  PowerSet(input, "", 0, resultSet);
          
                  //apply sorting 
                  var resultSorted = resultSet.OrderBy(l => l.Key.Length).ThenBy(l=>l.Key);
          
                  //print values
                  foreach(var keyValue in resultSorted)
                  {
                      Console.Write("{{{0}}}, ",keyValue.Key);
                  }
          
          
              }
          
              /// <summary>
              /// Computes the powerset of a string recursively
              /// based on the Algorithm http://www.ideserve.co.in/learn/generate-all-subsets-of-a-set-recursion
              /// </summary>
              /// <param name="input">Original input string</param>
              /// <param name="temp">Temporary variable to store the current char for the curr call</param>
              /// <param name="depth">The character position we are evaluating to add to the set</param>
              /// <param name="resultSet">A hash list to store the result</param>
              public static void PowerSet(string input, string temp, int depth, Dictionary<string, string> resultSet)
              {
          
                  //base case
                  if(input.Length == depth)
                  {
                      //remove duplicate characters
                      string key = new string(temp.ToCharArray().Distinct().ToArray());
          
                      //if the character/combination is already in the result, skip it
                      if (!resultSet.ContainsKey(key))
                          resultSet.Add(key, key);
          
                      return;//exit 
                  }
          
                  //left
                  PowerSet(input, temp, depth + 1, resultSet);
          
                  //right
                  PowerSet(input, temp + input[depth], depth + 1, resultSet);
          
              }
          

          【讨论】:

            【解决方案9】:

            PowerSet 将打印元素的所有组合 例如 [123] 将形成 123,12,13,23,1,2,3

            利用树的概念,我们可以很容易地找到幂集的值

            让每次添加一个元素或删除一个元素

                                abc
                       a                   " "
                   ab     a             b       " "
              abc   ab  ac  a         bc   b   c   " "
            

            这里先加了a而不加so树形的“a”和“”子元素 现在取一个常量并为其添加'b'而不添加'b'然后它将为'a'创建另一个子树,就像我们添加和删除元素直到我们到达末尾一样。

            这里是添加元素和删除元素的方法 powerset(str,i+1,cur+str.charAt(i)); powerset(str,i+1,cur);

            import java.io.*;
            import java.util.*;
            import java.lang.Math;
            
            class Demo{
                
                public static void main(String args[]) {
                    String str="123";
                    String str1="";
                    int r=0;
                    powerset(str,r,str1);
                    
                }
                
                public static void powerset(String str,int i,String cur){
                    
                    if(i==str.length()){
                        
                        System.out.println(cur);
                        return;
                        
                    }
                    
                    powerset(str,i+1,cur+str.charAt(i));
                    powerset(str,i+1,cur);
                     
                     
                }
                
            }
            

            【讨论】:

              【解决方案10】:

              字符串 "abc" 的幂集 (P) 包含两种类型的元素:字符 'a' 本身及其与 P('bc') 元素的组合。 类似地,P('bc') 包含字符 'b' 及其与 P('c') 元素的组合。 并且 P('c') 包含字符 'c' 及其与空字符串的组合。

              现在 make function powerSet(string input, string substring="") 这将打印子字符串,它表示输入字符串的第一个元素与子字符串的组合。

              基本条件:当输入字符串长度为0时,打印子字符串。

              递归条件: 1)。 Call powerSet( input[1: input.length()], substring ) #这是字符串的幂集元素,不包括第0个索引字符 2)。 call powerSet(input[1: input.length()], substring+input[0]) # 这是为了组合。

              #include<iostream>
              #include<string>
              using namespace std;
              
              void powerSet(string input,string substring){
              
                  if(input.length()==0){
                      cout<<substring<<", ";
                      return;
                  }
                  string op1=substring;
                  string op2=substring + input[0];    
                  powerSet(input.substr(1),op1);
                  powerSet(input.substr(1),op2);
                  return;
              }
              int main(){
                  string input="abc";
                  powerSet(input);
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-06-09
                • 2019-05-14
                • 2020-05-30
                • 2010-10-21
                • 2017-10-11
                相关资源
                最近更新 更多