递归组合

时间:2011-09-03 09:45:15

标签: algorithm math recursion

我正在尝试编写一个递归函数来获取给定列表的所有组合。

Eg. set = ABC
1. AAA
2. AAB
3. AAC
4. ABA 
N. CCC

我想要这个代码的递归版本,这样我就可以获得任意大小的组合:

for i=0; i<S.size(); i++ {
   for j=0; j<S.size(); j++ {
      for k=0; k<S.size(); k++ {

         combo[0] = S[i];
         combo[1] = S[j];
         combo[2] = S[k];
         combinations.push(combo);

      }
   }
}

我在解决这个问题时遇到了一些麻烦。到目前为止,我想我需要找到一个任意深度来阻止再次诅咒。

编辑:我更喜欢伪代码解决方案,我没有在C ++中实现它

6 个答案:

答案 0 :(得分:4)

鉴于您希望输出AABABA,您正在寻找排列而不是组合。特别是,您正在寻找一组大小 k 的唯一排列,其中元素是从一组 n 标记中替换而来的。组合的数量是 n + k-1 C k ,而排列的数量是 n k

说明这两个概念的伪代码:

build_combinations (tokens, set_size)
  Arrangements combos
  if (set_size == 0)
    combos.add ("")
  else
    Comment: tail_substrings of "ABC" is ("ABC", "BC", "C").
    foreach tail (tail_substrings (tokens))
      foreach sub_combo (build_combinations (tail, set_size-1))
        combos.add (tail.first() + sub_combo)
  return combos

build_permutations (tokens, set_size)
  Arrangements perms
  if (set_size == 0)
    perms.add ("")
  else
    sub_perms = build_permutations (tokens, set_size-1)
    foreach token (tokens)
      foreach perm (sub_perms)
        perms.add (cur_token + *rem_iter)
  return perms

正在运行的C ++实现:

#include <string>
#include <vector>

typedef std::string::const_iterator StringIterator;
typedef std::vector<std::string> Arrangements;
typedef Arrangements::const_iterator ArrangementsIterator;

Arrangements build_combinations (const std::string & tokens, unsigned set_size)
{
  Arrangements combos;
  if (set_size == 0) {
    combos.push_back ("");
  }   
  else {
    for (StringIterator token_iter = tokens.begin();
         token_iter != tokens.end();
         ++token_iter) {
      std::string cur_token(1, *token_iter);
      std::string rem_tokens(token_iter, tokens.end());
      Arrangements rem_combos = build_combinations (rem_tokens, set_size-1);
      for (ArrangementsIterator rem_iter = rem_combos.begin();
           rem_iter != rem_combos.end();
           ++rem_iter) {
         combos.push_back (cur_token + *rem_iter);
      }
    }
  }   
  return combos;
}   

Arrangements build_permutations (const std::string & tokens, unsigned set_size)
{
  Arrangements perms;
  if (set_size == 0) {
    perms.push_back ("");
  }
  else {
    Arrangements rem_perms = build_permutations (tokens, set_size-1);
    for (StringIterator token_iter = tokens.begin();
         token_iter != tokens.end();
         ++token_iter) {
      std::string cur_token(1, *token_iter);
      for (ArrangementsIterator rem_iter = rem_perms.begin();
           rem_iter != rem_perms.end();
           ++rem_iter) {
         perms.push_back (cur_token + *rem_iter);
      }
    }
  }
  return perms;
}

答案 1 :(得分:2)

我认为迭代解决方案会更有效率,并且可以编写它以支持任意维度和符号数量。代码是用C ++编写的,但是我很自然地保持简单,以便您可以轻松地转换为伪代码或其他语言:

#include <vector>
#include <cassert>
#include <iostream>

void generate_combinations(const std::vector<char>& symbols, const unsigned int dimension, std::vector<std::vector<char> >& output)
{
    assert( symbols.size() ); // terminate the program if condition not met
    std::vector<char> current_output(dimension);
    std::vector<unsigned int> current_combo(dimension + 1, 0);
    const unsigned int max_symbol_idx = symbols.size() - 1;
    size_t current_index = 0;
    while (current_combo.back() == 0) {
        // add current combination
        for (unsigned int i = 0; i < dimension; ++i) {
            current_output[i] = symbols[current_combo[i]];
        }
        output.push_back(current_output);

        // move to next combination
        while (current_index <= dimension && current_combo[current_index] == max_symbol_idx) {
            current_combo[current_index] = 0;
            ++current_index;
        }
        if (current_index <= dimension) {
            ++current_combo[current_index];
        }
        current_index = 0;
    }
}

int main()
{
    const unsigned int dimension = 3;
    std::vector<char> symbols(4);   
    symbols[0] = 'A';
    symbols[1] = 'B';
    symbols[2] = 'C';
    symbols[3] = 'D';
    std::vector<std::vector<char> > output;
    generate_combinations(symbols, dimension, output);
    for (unsigned int i = 0; i < output.size(); ++i) {
        for (unsigned int j = 0; j < dimension; ++j) {
            std::cout << output[i][j]; // write symbol to standard output
        }
        std::cout << std::endl; // write new line character
    }
}

输出应为:

  

AAA BAA CAA DA​​A ABA BBA CBA DBA ACA BCA CCA DCA ADA BDA CDA DDA AAB   BAB CAB DAB ABB BBB CBB DBB ACB BCB CCB DCB ADB BDB CDB DDB AAC BAC   CAC DAC ABC BBC CBC DBC ACC BCC CCC DCC ADC BDC CDC DDC AAD BAD CAD   DAD ABD BBD CBD DBD ACD BCD CCD DCD ADD BDD CDD DDD

如果您希望最后位置的符号变化最快,只需反转生成输出的每一行的内容。

当然,您可以将generate_combinations作为模板功能,并使其与char以外的其他类型一起使用。

============更新=================

递归解决方案当然更优雅:

void add_next_symbol(const std::vector<char>& symbols, const unsigned int dimension, std::vector<char>& current_output, std::vector<std::vector<char> >& output)
{
    if (dimension == 0) {
        output.push_back(current_output);
    } else {
        for (unsigned int i = 0; i < symbols.size(); ++i) {
            current_output.push_back(symbols[i]);
            add_next_symbol(symbols, dimension - 1, current_output, output);
            current_output.pop_back();
        }
    }
}

void generate_combinations_recursive(const std::vector<char>& symbols, const unsigned int dimension, std::vector<std::vector<char> >& output)
{
    std::vector<char> current_output;
    add_next_symbol(symbols, dimension, current_output, output);
}

在第一个程序中使用它代替generate_combinations函数。它应该给你与以前相同的输出。

答案 2 :(得分:1)

这是我的Java解决方案:

public class Combination {
  public List<String> recurse( String orig, int len ) {
    if( len == 0 ) {
      List<String> arr = new ArrayList<String>();
      arr.add("");
      return arr;
    } else {
      List<String> arr  = new ArrayList<String>();
      List<String> subs = recurse(orig, len - 1);

      for( int i = 0; i < orig.length(); i++ ) {
        String cur = Character.toString(orig.charAt(i));

        for( String sub : subs ) {
          arr.add(cur + sub);
        }
      }

      return arr;
    }
  }

  public static void main(String[] args) {
    String set = "ABC";

    Combination c = new Combination();
    for( String s : c.recurse(set, set.length()) ) {
      System.out.println(s);
    }
//    for( int i = 0; i < set.length(); i++ ) {
//      for( int j = 0; j < set.length(); j++ ) {
//        for( int k = 0; k < set.length(); k++ ) {
//          StringBuilder s = new StringBuilder();
//          s.append(set.charAt(i));
//          s.append(set.charAt(j));
//          s.append(set.charAt(k));
//          
//          System.out.println(s.toString());
//        }
//      }
//    }
  }
}

我包含了迭代的一个,因为我没有意识到你需要一个递归的解决方案。让我从伪代码的角度来解释它:

public List<String> recurse( String orig, int len ) {
  if( len == 0 ) {
    List<String> arr = new ArrayList<String>();
    arr.add("");
    return arr;
  } else {
    List<String> arr  = new ArrayList<String>();
    List<String> subs = recurse(orig, len - 1);

    for( int i = 0; i < orig.length(); i++ ) {
      String cur = Character.toString(orig.charAt(i));

      for( String sub : subs ) {
        arr.add(cur + sub);
      }
    }

    return arr;
  }
}

该函数返回可能的所有组合的列表。我首先在脑海中定义结果集,从而想到了这个问题。结果集由一个字符串数组组成,这些字符串的长度与原始字符串的长度相同,而表示每个子字符串,前面的字符可以是原始字符串中的任何字符。就是这样。

所以我们假设我们有一个函数可以生成每个子字符串并对其余部分进行处理。

Array somearray;

for( int i = 0; i < orig.length(); i++ ) {
  for( String s : getSubstrings() ) {
    Array.add( originalString.charAt(i) + s );
  }
}

然后生成子串,这是完全相同的问题,但长度比当前字符串小1。这是完全相同的功能(这就是它的递归方式)。我们只需要基本情况​​,当长度为0时,在这种情况下,我们返回一个空字符串,附加到每个字符。

很抱歉,如果您不理解我的解释,我不确定如何做到最好。 Java与伪代码非常接近,因此不难理解。

答案 3 :(得分:0)

//使用递归的唯一组合

//在这里,我修改了使用前缀来继承递归以仅仅在索引上执行//以便对象可以作为参数而不仅仅是字符串的想法。

public static void main(String[] args) {


    int k = 20;

    Object[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    Object[] chars = { 'a', 'b', 'c', 'd', 'e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
    Object[] aux = new Object[k];



    long start = System.currentTimeMillis();
    combination(chars, 0, 0, k, aux);
    //combination(nums, 0, 0, k, aux);

    System.out.println("Time: "+ (System.currentTimeMillis()-start));
}

public static void combination(Object[] s, int index, int next, int k,
        Object[] aux) {

    //this is the base case
    //if the index has reached k then print out the aux which holds the combination
    if (index == k) {
        show(aux);          
    }else{//here you spawn loops

        for (int i = next; i < s.length; i++) {

            aux[index] = s[i];
            combination(s, index + 1, i + 1, k, aux);
        }
    }
}

private static void show(Object[] x) {
    for (int i = 0; i < x.length; i++)
        System.out.print(x[i] + " ");
    System.out.println();
}

}

答案 4 :(得分:0)

@Jan Even tho&#39;看来你对我前面的回答的暗示是有些随意的,我接受了你的挑战,并提出了这个答案,满足了你自己想要从整个集合中调用任何大小的组合的愿望。

递归地,一个非常简单的答案,combo,在Free Pascal中。 n是整个集合的大小,k是请求的子集大小。

    procedure combinata (n, k :integer; producer :oneintproc);

        procedure combo (ndx, nbr, len, lnd :integer);
        begin
            for nbr := nbr to len do begin
                productarray[ndx] := nbr;
                if len < lnd then
                    combo(ndx+1,nbr+1,len+1,lnd)
                else
                    producer(k);
            end;
        end;

    begin
        combo (0, 0, n-k, n-1);
    end;

producer处理为每个组合制作的productarray[]

答案 5 :(得分:-1)

基于一个非常简单和优雅的递归解决方案 Adam在一个不同的问题下:&#39;算法返回所有组合 来自n&#39;的k个元素,你可以在其他地方找到。

然而,亚当的答案提供了从中获得所有组合的方法 给定的一组,并不完全适合他的问题 给出了答案,但我发现他的答案符合我的目的 研究完美。无论我在哪里找到它,我都会寻找价值。和 它确实适合这个问题。

我使用Free Pascal中的Open Arrays开发了以下程序 生成任何给定数组中的所有项目组合。打开阵列 允许任意和动态可变数量的项目, 每个项目可以是任何类型的变量或记录。这个程序有 长整数项,但也可用于指向其他项的指针 变量。我用这种方式来确定最有效的方法 通过检查将金属棒切割成各种长度的短棒 不同长度的较短条的各种可能组合 适合原料棒。

程序组合为每个可能的组合递归一次, 但递归的深度只比数字深一级 等待&#39;中的项目源数组。将参数传递给 通过引用的组合过程不是必需的,但是这样做 将操作内存需求减少了近一半。

    program combinata;

    uses
        SYSUTILS;

    type
        combarry = array of longint;

    var
        testc, testp :combarry;

        procedure produce (var cmb :combarry);
        var  x :integer;
        begin
            for x := 0 to length(cmb)-1 do begin
                if x > 0 then write(',');
                write(cmb[x]:0);
            end;
            writeln;
        end;

    procedure combo (var current, pending :combarry);
    var
        x, len  :integer;
        newcurrent, newpending  :combarry;

    begin
        if length(pending) = 0 then
            if length(current) > 0 then produce(current) else
        else begin

            {append 1st element of pending as the last element on current}
            newcurrent := current;
            len := length(newcurrent);
            setlength(newcurrent,len+1);
            newcurrent[len] := pending[0];

            {remove the first element from pending}
            len := length(pending) - 1;
            setlength(newpending,len);
            for x := len downto 1 do newpending[x-1] := pending[x];

            combo(newcurrent,newpending);
            combo(current,newpending);
        end;
    end;

    begin
        setlength(testc,0);
        setlength(testp,4);
        testp[0] := 5;
        testp[1] := 10;
        testp[2] := 15;
        testp[3] := 20;
        combo(testc, testp);
        writeln('*');
    end.

    Running this produces:
    5,10,15,20
    5,10,15
    5,10,20
    5,10
    5,15,20
    5,15
    5,20
    5
    10,15,20
    10,15
    10,20
    10
    15,20
    15
    20
    *