【问题标题】:how to write iterative algorithm for generate all subsets of a set?如何编写迭代算法来生成集合的所有子集?
【发布时间】:2014-04-12 08:53:26
【问题描述】:

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

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;
    }
}

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

【问题讨论】:

  • 用循环和堆栈替换递归
  • 请提供示例代码。

标签: algorithm recursion set iteration recursive-backtracking


【解决方案1】:

您可以使用二进制计数器方法。任何长度为 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,这使代码确实更短,但我使用字节数组来更好地说明该过程。

【讨论】:

    【解决方案2】:

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

    • 0 计数(即简单的for循环)到2<sup>numberOfElements</sup> - 1

    • 如果我们查看上面用于以二进制计数的变量,每个位置的数字可以被认为是一个标志,指示集合中相应索引处的元素是否应包含在该子集中。只需循环每个位(将余数除以 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 语句),每个部分由函数中任意两个递归调用之间的代码组成

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

    • 循环将从堆栈中弹出元素,执行相应的代码段

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

    • return 替换为适当的break 语句

    【讨论】:

      【解决方案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}
      

      【讨论】:

        【解决方案4】:

        基本上你想要的是 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
         }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-11-07
          • 1970-01-01
          • 2017-09-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多