整数子集的递归枚举?

时间:2011-03-08 23:53:01

标签: iphone objective-c math

我有一个NSNrray的NSNumber,其整数值如[1,10,3]。我想得到这些数字的所有可能子集的总和。例如,对于1,10和3,我会得到:

1,10,3,1 + 10 = 11,1 + 3 = 4,10 + 3 = 13,1 + 10 + 3 = 14

有2 ^ n种可能的组合。我理解它的数学但是我很难将它放入代码中。那么我怎么能把它放到一个方法中,该方法将采用初始数字数组并返回一个包含所有子集总和的数组?例如-(NSArray *) getSums:(NSArray *)numbers;

我知道结果会成倍增长,但我会将其用于小数组。

2 个答案:

答案 0 :(得分:4)

一个简单的递归解决方案 - 可能效率不高,而且未经测试,但总的想法应该是明确的。

- (NSArray*)getSums:(NSArray*)numbers {
    return [[self getSumsHelper:numbers startingFrom:0] copy];
}

- (NSMutableArray*)getSumsHelper:(NSArray*)numbers startingFrom:(NSUInteger)index {
    /* (1) */
    if (index >= [numbers count])
        return [NSMutableArray arrayWithObject:[NSNumber numberWithFloat:0]];

    /* (2) Generate all the subsets where the `index`th element is not included */
    NSMutableArray* result = [self getSumsHelper:numbers startingFrom:index+1];

    /* (3) Add all the cases where the `index`th element is included */
    NSUInteger i, n = [result count];
    float element = [[numbers objectAtIndex:index] floatValue];
    for (i = 0; i < n; i++) {
        float element2 = [[result objectAtIndex:i] floatValue];
        [result addObject:[NSNumber numberWithFloat:element+element2]];
    }

    return result;
}

问题的关键是生成给定数组的所有子集。这可以通过识别以下内容递归完成:

  1. 空数组的子集是空数组,空数组中的元素总和为零。这由标有(1)

  2. 的行处理
  3. 如果数组不为空,则让第一个元素为X.任何子集都包含X或不包含它。因此,我们将生成不包含X的数组的所有子集(有递归,由(2)标记,计算每个子集的总和,然后复制总和数组并将X添加到每个子集中重复部分中的总和(由(3)标记)。

  4. 如果原始数组中只有少数项目(例如,少于16个),那么您也可以在for循环中从0到2 n -1计数;然后,每个索引将对应于一个子集。可以通过在基数2中写i并从数组中选择与基数2形式中的数字1相对应的那些项来确定i子集中的元素。我相信这比我上面的解决方案效率更低。

答案 1 :(得分:1)

[警告:提前“聪明”的代码。做一些更简单的事情你可能会更好。但这很有趣,有些技术值得了解。]

如果集合足够小(它们最好是,因为否则你将耗尽内存和时间),你可以使用n元素集的子集== n-位数。所以不需要递归:从0到1的循环&lt;

这样做的一个缺点是,对于每个子集,您可能需要每次都从头开始考虑所有元素。因此,下一个技巧:使用格雷码(http://en.wikipedia.org/wiki/Gray_code#Constructing_an_n-bit_Gray_code),使得集合k具有对应于k ^(k> 1)中的1比特的元素。现在,每个子集与其前一个子集的区别仅在于一个位,您可以将其与另一个异或操作隔离。

好的,但是现在你有一个不同的问题:你有2的幂,想知道数组中哪个元素对应。所以请参阅http://www-graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup并查看开头“如果你知道v是2的幂”。

所以代码最终看起来像这样(注意:完全未经测试)......

static const int bitPositions[32] = {
  0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 
  31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};

NSUInteger n = [numbers count];
NSUInteger lim = 1<<n;
NSUInteger k, prevSubset=0;
float prevSum = 0;
NSMutableArray * result = [NSMutableArray arrayWithCapacity: lim];
[result replaceObjectAtIndex: 0 withObject: [NSNumber numberWithFloat: prevSum]]; /* empty subset */
for (k=1; k<lim; ++k) {
  NSUInteger thisSubset = k^(k>>1);
  NSUInteger changed = thisSubset^prevSubset;
  int index = bitPositions[(changed * 0x077CB531U) >> 27];
  float delta = [[numbers objectAtIndex: index] floatValue];
  if (thisSubset&changed) prevSum += delta; else prevSum -= delta;
  [result replaceObjectAtIndex: k withObject: [prevsum floatValue]];
}

进一步的警告:除了“聪明”,因此可能有缺陷和不可维护之外,上面会累积整个计算中的任何浮点错误。所以,如果你要采取这种方法,这里有一个更好的方法(注意:代码与最后一批一样未经测试):

static const int bitPositions[32] = {
  0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 
  31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};

NSUInteger n = [numbers count];
NSUInteger lim = 1<<n;
NSUInteger k;
NSMutableArray * result = [NSMutableArray arrayWithCapacity: lim];
[result replaceObjectAtIndex: 0 withObject: [NSNumber numberWithFloat: prevSum]]; /* empty subset */
for (k=1; k<lim; ++k) {
  NSUInteger firstOne = k & ~(k-1); /* one 1-bit (as it happens, the lowest) */
  NSUInteger predecessor = k^firstOne; /* what we get by removing firstOne */
  int index = bitPositions[(firstOne * 0x077CB531U) >> 27];
  float smaller = [[result objectAtIndex: predecessor] floatValue];
  float delta = [[numbers objectAtIndex: index] floatValue];
  [result replaceObjectAtIndex: k withObject: [(smaller+delta) floatValue]];
}

为了获得更高的效率,如果您真的这样做,您可能首先要构建一个数组,其中不包含上面bitPositions的魔术位索引,而是来自numbers的相应值,从而为每个子集保存一个NSArray访问权限。如果您关心效率,那么您应该将numbers复制到float的纯粹C风格的数组中,这样您就不必打电话给floatValue。< / p>