在一个集合中查找一组数字,在另一个集合中添加一个数字

时间:2010-07-05 10:44:28

标签: algorithm set combinations heuristics np-complete

对于我正在制作的游戏,我的情况是我有一个数字列表 - 比如[7,4,9,1,15,2](为此命名为A) - 以及另一个列表数字 - 比如[11,18,14,8,3](名为B) - 提供给我。目标是在A中找到所有数字组合,这些数字组合加起来为B中的数字。例如:

  • 1 + 2 = 3
  • 1 + 7 = 8
  • 2 + 9 = 11
  • 4 + 7 = 11
  • 1 + 2 + 4 + 7 = 14
  • 1 + 2 + 15 = 18
  • 2 + 7 + 9 = 18

......等等。 (出于此目的,1 + 22 + 1相同。)

对于像这样的小型列表,只是暴力破坏组合是微不足道的,但我面临着看到数千到数万这些数字的可能性,并将在应用程序的生命周期内重复使用这个例程。有没有任何一种优雅的算法可以在100%覆盖率的合理时间内完成此任务?如果做不到这一点,我能找到任何一种体面的启发式方法,可以在合理的时间内给我一套“足够好”的组合吗?

我正在寻找伪代码或任何体面流行和可读语言的算法(注意“和”那里......;)甚至只是英文描述如何实现这种类型搜索。


已编辑添加:

到目前为止提供了大量有用的信息。谢了,兄弟们!总结一下:

  • 问题是NP-Complete,所以没有办法在合理的时间内达到100%的准确率。
  • 可以将问题视为subset sumknapsack问题的变体。两者都有众所周知的启发式方法,可以适应这个问题。

保持想法的到来!再次感谢!

4 个答案:

答案 0 :(得分:5)

这个问题是NP-Complete ...这是子集和问题的一些变化,已知是NP-Complete(实际上,子集和问题比你的更容易)。

请在此处阅读以获取更多信息: http://en.wikipedia.org/wiki/Subset_sum_problem

答案 1 :(得分:2)

如评论所述,数字范围仅为1到30,问题有一个快速的解决方案。我在C中进行了测试,对于您给出的示例,它只需要几毫秒,并且可以很好地扩展。复杂度为O(n + k),其中n是列表A的长度,k是列表B的长度,但具有高常数因子(有28.598个可能性来获得从1到1的总和30)。

#define WIDTH 30000
#define MAXNUMBER 30

int create_combination(unsigned char comb[WIDTH][MAXNUMBER+1], 
                       int n, 
                       unsigned char i, 
                       unsigned char len, 
                       unsigned char min, 
                       unsigned char sum) {
    unsigned char j;

    if (len == 1) {
        if (n+1>=WIDTH) {
            printf("not enough space!\n");
            exit(-1);
        }
        comb[n][i] = sum;
        for (j=0; j<=i; j++)
            comb[n+1][j] = comb[n][j];
        n++;
        return n;
    }

    for (j=min; j<=sum/len; j++) {
        comb[n][i] = j;
        n = create_combination(comb, n, i+1, len-1, j, sum-j);
    }

    return n;
}

int main(void)
{
    unsigned char A[6] = { 7, 4, 9, 1, 15, 2 };
    unsigned char B[5] = { 11, 18, 14, 8, 3 };

    unsigned char combinations[WIDTH][MAXNUMBER+1];
    unsigned char needed[WIDTH][MAXNUMBER];
    unsigned char numbers[MAXNUMBER];
    unsigned char sums[MAXNUMBER];
    unsigned char i, j, k;
    int n=0, m;

    memset(combinations, 0, sizeof combinations);
    memset(needed, 0, sizeof needed);
    memset(numbers, 0, sizeof numbers);
    memset(sums, 0, sizeof sums);

    // create array with all possible combinations
    // combinations[n][0] stores the sum
    for (i=2; i<=MAXNUMBER; i++) {
        for (j=2; j<=i; j++) {
            for (k=1; k<=MAXNUMBER; k++)
                combinations[n][k] = 0;
            combinations[n][0] = i;
            n = create_combination(combinations, n, 1, j, 1, i);
        }
    }

    // count quantity of any summands in each combination
    for (m=0; m<n; m++)
        for (i=1; i<=MAXNUMBER && combinations[m][i] != 0; i++)
            needed[m][combinations[m][i]-1]++;

    // count quantity of any number in A
    for (m=0; m<6; m++)
        if (numbers[A[m]-1] < MAXNUMBER)
            numbers[A[m]-1]++;

    // collect possible sums from B
    for (m=0; m<5; m++)
        sums[B[m]-1] = 1;

    for (m=0; m<n; m++) {
        // check if sum is in B
        if (sums[combinations[m][0]-1] == 0)
            continue;

        // check if enough summands from current combination are in A
        for (i=0; i<MAXNUMBER; i++) {
            if (numbers[i] < needed[m][i])
                break;
        }

        if (i<MAXNUMBER)
            continue;

        // output result
        for (j=1; j<=MAXNUMBER && combinations[m][j] != 0; j++) {
            printf(" %s %d", j>1 ? "+" : "", combinations[m][j]);
        }
        printf(" = %d\n", combinations[m][0]);
    }

    return 0;
}

1 + 2 = 3
1 + 7 = 8
2 + 9 = 11
4 + 7 = 11
1 + 4 + 9 = 14
1 + 2 + 4 + 7 = 14
1 + 2 + 15 = 18
2 + 7 + 9 = 18

答案 2 :(得分:1)

听起来像背包问题(请参阅http://en.wikipedia.org/wiki/Knapsack_problem。在该页面上,他们还解释说问题通常是NP完全的。

我认为这意味着如果你想找到所有有效的组合,你只需要很多时间。

答案 3 :(得分:1)

这是subset sum problem的一个小概括。一般来说,它是NP完全的,但只要所有数字都是整数并且B中的最大数量相对较小,我链接的维基百科文章中描述的伪多项式解决方案应该可以解决问题。 / p>