【问题标题】:Generating all subsets from a single set从单个集合中生成所有子集
【发布时间】:2012-06-03 11:19:40
【问题描述】:

我试图理解从一组中生成所有子集的代码。这是代码

#include <stdio.h>

/* Applies the mask to a set like {1, 2, ..., n} and prints it */
void printv(int mask[], int n) {
    int i;
    printf("{ ");
    for (i = 0; i < n; ++i)
        if (mask[i])
            printf("%d ", i + 1); /*i+1 is part of the subset*/
    printf("\\b }\\n");
}

/* Generates the next mask*/
int next(int mask[], int n) {
    int i;
    for (i = 0; (i < n) && mask[i]; ++i)
        mask[i] = 0;

    if (i < n) {
        mask[i] = 1;
        return 1;
    }
    return 0;
}

int main(int argc, char *argv[]) {
    int n = 3;

    int mask[16]; /* Guess what this is */
    int i;
    for (i = 0; i < n; ++i)
        mask[i] = 0;

    /* Print the first set */
    printv(mask, n);

    /* Print all the others */
    while (next(mask, n))
        printv(mask, n);

    return 0;
}

我不明白 next 函数中 for (i = 0; (i &lt; n) &amp;&amp; mask[i]; ++i) 这条线背后的逻辑。这里如何生成下一个掩码?

代码和算法看这里: http://compprog.wordpress.com/2007/10/10/generating-subsets/

【问题讨论】:

  • 为什么不使用调试器单步执行此操作,或者添加打印语句?

标签: c++


【解决方案1】:

这只是counting in binary 的一个实现。基本思想是将最不重要的(最后一个)零更改为一,并将其​​后面的所有零更改为零。如果解释为二进制数,“下一个”掩码将比上一个“多一个”。

由于数组是按个位在前排列的,所以从传统的数字符号来看是向后的。

与其使用布尔值数组,不如使用一个数字的二进制表示中的位和++ 运算符。

int next(int &mask, int n) { // using C++ reference
    if ( mask == ( 1u << n ) - 1 ) return 0;
    ++ mask;
    return 1;
}

void printv(int mask, int n) {
    int i;
    printf("{ ");
    for (i = 0; i < n; ++i)
        if (mask & ( 1 << i ) )
            printf("%d ", i + 1); /*i+1 is part of the subset*/
    printf("\\b }\\n");
}

自从您这样标记问题以来,我已经使用了一点 C++,但发布的代码是纯 C。

【讨论】:

  • Wat 究竟是指 &amp;&amp; mask[i];next 函数的 for 循环中吗?如果您还可以解释一下将mask[i] 设置为1 if i&lt;n?
  • &amp;&amp; mask[i] 只会继续将位设置为零,只要它们最初是 1。它只会在 i&lt;n 时将该位设置为 1,因为您只需要 n 位掩码,任何更多位都会溢出数组。
【解决方案2】:

去年我参加了第 6 届 ITAT 比赛的 C 语言竞赛,我通过使用掩码生成所有组合解决了第二个问题(尽管它可能不是该问题的最佳解决方案。)

当您尝试导出{a,b,c} 的所有子集时,您可以这样做:

  1. 您可能会或可能不会采用第一个元素a
  2. 可能会或可能不会采用第二个元素b
  3. c 也一样。

因此,您最终得到了一组 3 种接受或不接受的选择。这可以用二进制或布尔值表示:表示采用1,而不采用0

你得到以下八个掩码:(按a,b,c的顺序)

000 100 010 110 001 101 011 111

生成110的下一个掩码:

  1. 元素 0 是 1。切换到0
  2. 元素 1 是1。切换到0
  3. 元素 2 是0。切换到1
  4. 现在您有了001,它是下一个掩码,它会生成子集{c}

for (i = 0; (i &lt; n) &amp;&amp; mask[i]; ++i) 正是这样做的。

  1. 从元素 0 开始。
  2. 虽然(i 不超过您的掩码长度并且元素 i 是 1
  3. 执行将 i 翻转为 0 和 ++i 的正文代码(转到下一个元素)。转到 2(检查)。

如果当前掩码是111(最后一个掩码),则next() 函数只返回1 表示END。

(P.S. 一个非零整数总是代表真。)

【讨论】:

    【解决方案3】:

    问题中的循环从数组的开头开始,并将所有 1 设置为 0,直到遇到 0。下一条语句将此 0 设置为 1(如果可能)。所以会发生什么:0,0,0 -> 1,0,0 -> 0,1,0 -> 1,1,0 -> 0,0,1...我不是铁杆 C 程序员,但我认为这可以通过使用位字段并迭代递增 1 来完成。

    【讨论】:

    • 只要元素的数量足够小以适应位域,这确实会容易得多。当然,当n 太大时,这并不重要,因为打印2^n 子集将花费很长时间。
    【解决方案4】:
    for (i = 0; (i < n) && mask[i]; ++i)
    
    1. 为:
    2. 从 0 开始,每次将 i 加 1
    3. 在 i 小于 n 并且设置了掩码数组中位置 i 的位时不要停止

    真的很简单:for 语句的 3 个部分:初始状态;结束条件;操作。

    所以如果你能理解 for (i=0; i &lt; 5; i++) 的意思是从 0 开始,每次 i 加 1 直到它不能小于 5,你就可以理解你问的更复杂的 for 循环了。

    在这种情况下,它通过循环寻找未设置的掩码的第一个元素,清除每个元素,然后它执行一些其他操作 - 即如果没有设置掩码位,它到达数组的末尾。在我看来,这是一种将数组的一个元素设置为 1 的简单方法,依次得到结果:100、010、001

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-10
      • 2013-10-04
      • 1970-01-01
      相关资源
      最近更新 更多