如何编写迭代算法来生成集合的所有子集?

时间:2014-03-09 08:32:29

标签: algorithm recursion set iteration recursive-backtracking

我编写了递归回溯算法来查找给定集合的所有子集。

void backtracke(int* a, int k, int n)
{
    if (k == n)
    {
        for(int i = 1; i <=k; ++i)
        {
            if (a[i] == true)
            {
                std::cout << i << " ";
            }
        }
        std::cout << std::endl;
        return;
    }
    bool c[2];
    c[0] = false;
    c[1] = true;
    ++k;
    for(int i = 0; i < 2; ++i)
    {       
        a[k] = c[i];
        backtracke(a, k, n);
        a[k] = INT_MAX;
    }
}

现在我们必须编写相同的算法,但是以迭代的形式,该怎么做?

4 个答案:

答案 0 :(得分:10)

您可以使用二进制计数器方法。任何长度为n的唯一二进制字符串表示一组n个元素的唯一子集。如果从0开始并以2 ^ n-1结束,则覆盖所有可能的子集。计数器可以很容易地以迭代的方式实现。

Java中的代码:

  public static void printAllSubsets(int[] arr) {
    byte[] counter = new byte[arr.length];

    while (true) {
      // Print combination
      for (int i = 0; i < counter.length; i++) {
        if (counter[i] != 0)
          System.out.print(arr[i] + " ");
      }
      System.out.println();

      // Increment counter
      int i = 0;
      while (i < counter.length && counter[i] == 1)
        counter[i++] = 0;
      if (i == counter.length)
        break;
      counter[i] = 1;
    }
  }

请注意,在Java中,可以使用BitSet,这使得代码真的更短,但我使用了一个字节数组来更好地说明这个过程。

答案 1 :(得分:7)

有几种方法可以为此问题编写迭代算法。最常见的建议是:

  • 02numberOfElements - 1

  • 的计数(即简单的for循环)
  • 如果我们查看上面用于计算二进制数的变量,每个位置的数字可以被认为是一个标志,指示该集合中相应索引处的元素是否应该包含在该子集中。只需循环遍历每个位(将余数除以2,然后除以2),包括输出中的相应元素。

示例:

输入:{1,2,3,4,5}

我们开始计算0,这是二进制的00000,这意味着没有设置标志,所以没有包含任何元素(如果你不想要,这显然会被跳过)空子集) - 输出{}

然后1 = 00001,表示只包含最后一个元素 - 输出{5}

然后2 = 00010,表示只包含倒数第二个元素 - 输出{4}

然后3 = 00011,表示将包含最后两个元素 - 输出{4,5}

依此类推,一直到31 = 11111,表示将包含所有元素 - 输出{1,2,3,4,5}

*实际上在代码方面,将它转到头部会更简单 - {1}输出00001,考虑到第一个余数乘以2将对应于第0个元素的标志,第二个余数,第一个元素等,但为了说明的目的,上述内容更简单。


更一般地,任何递归算法都可以更改为迭代算法,如下所示:

  • 创建一个由部分组成的循环(想想switch-statement),每个部分由函数中任意两个递归调用之间的代码组成

  • 创建一个堆栈,其中每个元素包含函数中的每个必要的局部变量,并指示我们正忙于哪个部分

  • 循环会弹出堆栈中的元素,执行相应的代码部分

  • 每次递归调用都将被替换为首先将它自己的状态添加到堆栈,然后是被调用状态

  • return替换为适当的break语句

答案 2 :(得分:3)

George算法的一点Python实现。也许它会帮助别人。

def subsets(S):
    l = len(S)
    for x in range(2**l):
        yield {s for i,s in enumerate(S) if ((x / 2**i) % 2) // 1 == 1}

答案 3 :(得分:1)

基本上你想要的是P(S)= S_0 U S_1 U ... U S_n其中S_i是从S取i个元素所包含的所有集合的集合。换句话说,如果S = {a,b,c然后S_0 = {{}},S_1 = {{a},{b},{c}},S_2 = {{a,b},{a,c},{b,c}}和S_3 = { a,b,c}。

我们到目前为止的算法是

set P(set S) {
    PS = {}
    for i in [0..|S|]
        PS = PS U Combination(S, i)
    return PS
}

我们知道| S_i | = nCi其中| S | = n。所以基本上我们知道我们将循环nCi次。您可以稍后使用此信息来优化算法。为了生成大小为i的组合,我提出的算法如下:

假设S = {a,b,c},那么你可以将0映射到a,1到b和2到c。并且对它们的影响是(如果i = 2)0-0,0-1,0-2,1-0,1-1,1-2,2-0,2-1,2-2。要检查一个序列是否是一个组合,你检查这些数字是否都是唯一的,并且如果你对数字进行置换,序列不会出现在其他地方,这将把上面的序列过滤到0-1,0-2和1-2稍后将其映射回{a,b},{a,c},{b,c}。如何生成上面的长序列可以遵循此算法

 set Combination(set S, integer l) {
     CS = {}
     for x in [0..2^l] {
         n = {}
         for i in [0..l] {
             n = n U {floor(x / |S|^i) mod |S|} // get the i-th digit in x base |S|
         }
         CS = CS U {S[n]}
     }
     return filter(CS) // filtering described above
 }