生成具有重复的集合元素的k组合的算法?

Posted

技术标签:

【中文标题】生成具有重复的集合元素的k组合的算法?【英文标题】:Algorithm to generate k-combinations of elements of set with repetition? 【发布时间】:2012-09-10 14:36:00 【问题描述】:

我正在寻找一种算法,该算法将一组两个元素 T = 0, 1k 作为输入,并生成所有 k-T 的组合,如下所示:

000
001
010
011
100
101
110
111

k 较小时,迭代实现很简单,如上例中的k=3。任何想法如何设计递归算法以使算法独立于k

【问题讨论】:

这是排列,组合顺序无关紧要。 【参考方案1】:

您可以递归地执行此操作。假设这是一个学习练习,我会给你伪代码而不是 C 程序:

generate (int pos, int element[T], int current[N])
    if pos == N
        print (current)
        return

    for i : 0..T
        current[pos] = element[i]
        generate(pos+1, element, current)

end

前三行是基本情况。 pos 从零开始,表示current 数组中需要被当前递归调用级别填充的位置。一旦pos 达到N,我们打印当前组合并返回到上一个级别。

下面三行是一个循环,类似于k=3时解决问题时的嵌套循环。 “嵌套”通过递归动态发生:您可以将递归调用的下一级视为另一级“循环嵌套”。本质上,递归解决方案允许您构建 N 嵌套循环,其中 N 仅在运行时才知道。

【讨论】:

【参考方案2】:

您不需要递归算法。如果您查看您的列表,您应该会看到“模式”。

这是从 0 到 2^k-1 的二进制数。因此,简单的解决方案就是数数。

for (i = 0; i < (1<<k); i++) 
    // generate the binary representation of i
    for (c = k-1 ; c >= 0; c--) 
       r = i >> c;
       printf((r & 1) ? "1" : "0");
    
    printf("\n");

编辑:如果你需要一个递归方法,你可以通过在长度上递归来轻松地做到这一点,我给出了一些伪代码(因为在我看来,递归只有在它是一些赋值时才有意义/作业,然后你应该自己做点什么):

print_all_combos(int k, char* prefix) 
   if (k == 0) 
      printf("%s\n", prefix);
      return;
   
   append(prefix, "0");
   print_all_combos(k - 1 , prefix);
   remove_last_char(prefix);
   append(prefix, "1");
   remove_last_char(k - 1, prefix);

并以 k 和一个空字符串作为参数调用它。

【讨论】:

【参考方案3】:

如果您在设计时知道 k,则使用 k 循环很容易生成所有 k 组合,即如果您想要所有 4 组合,则可以使用 4 个循环来完成:

for c1=0 to 1
  for c2=0 to 1
    for c3=0 to 1
      for c4=0 to 1
        print c1,c2,c3,c4

如果您在设计时不知道 k,您将需要一种模拟 k 循环的方法。这很简单,创建一个大小为 k 的数组并在索引 i 处存储 ci 的当前值(循环编号 i 索引)。

c : array[1..k]
fill(c,0) // initialize all the cells with 0
do 
  for i=1 to k
    print c[i]
while increment(c) // get next values

increment 获取下一个值,如果所有值都已使用,则返回 false,否则返回 true。

increment(c : array[1..k])
begin
  i=k
  do
    c[i]=c[i]+1;
    if c[i]=2 // i.e. MAX+1
      c[i]=0
      i=i-1 // incerment previous position
    else
      return true // increment done
    end if
  while (i>1)

  // here we need to increment the first position
  c[i]=c[i]+1
  if c[i]=2 // we looped thru all the values
    c[i]=0
    return false
  end if
  return true
end

这种方法可以推广到任何base中的任何循环(=每个循环的不同最大值)或具有不同的起始值、步骤等... 这种方法也可以推广到生成带有重复的字典组合等......谷歌里程表或看看TAOCP Knuth Volume 3 fascicle 2 and 3。

【讨论】:

【参考方案4】:

根据您提供的示例,我相信您指的是 k 排列,而不是组合。引用自***:

组合是从一个较大的组中选择多个事物的一种方式,其中(不像 排列)顺序无关紧要。

【讨论】:

这应该是评论,而不是答案。【参考方案5】:
#include<stdio.h>
#include<conio.h>
void calAns(int idx, int f[3]);
int main()

    int f[3];
    calAns(0,f);
    getch();
    return 0;


void calAns(int idx, int f[3])

    if(idx == 3)
    
        int i;
        for(i = 0; i<3; i++)
            printf("%d",f[i]);
        printf("\n");
        return;
    

    f[idx] = 0;
    calAns(idx+1, f);
    f[idx] = 1;
    calAns(idx+1, f);

【讨论】:

您的回答当然值得稍微解释一下。请参考***.com/help/how-to-answer。【参考方案6】:

感谢@Sergey Kalinichenko,我制作了一个小递归应用程序。 尽管这是 Java 代码,但我希望它会对某人有所帮助。

关键方法是generateCombinationsRecursively

测试类:

public class CombinationOKTest 

    CombinationOK combinationOK;

    @BeforeEach
    void setUp() 
        combinationOK = new CombinationOK();
    

    @Test
    void allCombinationsWithTwoElementsAndLengthThreeBinary() 
        List<Integer> elementList = List.of(0, 1);
        int combinationLength = 3;

        List<List<Integer>> combinationList =
                combinationOK.getAllCombinations(elementList, combinationLength);

        assertEquals(8, combinationList.size());
        assertEquals(List.of(
                        List.of(0, 0, 0),
                        List.of(0, 0, 1),
                        List.of(0, 1, 0),
                        List.of(0, 1, 1),
                        List.of(1, 0, 0),
                        List.of(1, 0, 1),
                        List.of(1, 1, 0),
                        List.of(1, 1, 1)),
                combinationList);
    

实现类:

public class CombinationOK 
    public List<List<Integer>> getAllCombinations(List<Integer> elementList,
                                                  int combinationLength) 
        List<List<Integer>> combinationList = new ArrayList<>();
        Integer[] combination = new Integer[combinationLength];

        generateCombinationsRecursively(elementList, combinationList, combination, 0);

        System.out.println();
        System.out.println(combinationList);
        return combinationList;
    

public class CombinationOK 
    public List<List<Integer>> getAllCombinations(List<Integer> elementList,
                                                  int combinationLength) 
        List<List<Integer>> combinationList = new ArrayList<>();
        Integer[] combination = new Integer[combinationLength];

        generateCombinationsRecursively(elementList, combinationList, combination, 0);

        System.out.println();
        System.out.println(combinationList);
        return combinationList;
    

/**
 *
 * Magic is done in this recursive method <code>generateCombinationsRecursively</code>.
 *
 * @param elementList elements that combinations are made of (T)
 * @param combinationList is resulting list of combinations as a result of recursive method
 * @param combination is array of elements, single combination with variable length (k)
 * @param position of one element from the list <code>elementList</code> in the <code>combination</code> array
 *
 */
private void generateCombinationsRecursively(List<Integer> elementList,
                                             List<List<Integer>> combinationList,
                                             Integer[] combination,
                                             int position) 
    if (position == combination.length) 
        System.out.println(Arrays.asList(combination));
        combinationList.add(new ArrayList<>(Arrays.asList(combination)));
        return;
    

    for (int i = 0; i < elementList.size(); i++) 
        combination[position] = elementList.get(i);
        generateCombinationsRecursively(
                elementList, combinationList, combination, position + 1);
    

总结:

主要功能在这个递归方法generateCombinationsRecursively

参数position 是可变的,取决于递归函数的深度。 position 参数的最大值是 combination 的列表大小 (k)。 如果达到最大值(k)(方法generateCombinationsRecursively的第一个块), 新的combination 被添加到生成的combinationList 列表中,递归结束。

方法generateCombinationsRecursively的第二个块是for循环 它遍历列表elementList 中的所有元素。 根据position 的值,combination 列表填充有元素 来自elementList (T)。接下来递归方法generateCombinationsRecursivelyposition 参数上使用重音调用,该参数递增,因此它指向 combination 中的下一个位置,该递归方法将填充下一个元素(来自 T)。

输出组合:

[0, 0, 0]
[0, 0, 1]
[0, 1, 0]
[0, 1, 1]
[1, 0, 0]
[1, 0, 1]
[1, 1, 0]
[1, 1, 1]

输出结果combinationList列表:

[[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]]

【讨论】:

以上是关于生成具有重复的集合元素的k组合的算法?的主要内容,如果未能解决你的问题,请参考以下文章

剑指 Offer II 082. 含有重复元素集合的组合

[转]容斥原理与多重集合{理论}

使用 MATLAB 生成所有重复组合

Python组合数据类型

Python组合数据类型

重复组合计数