10

对于我正在制作的游戏,我有一个数字列表——比如 [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 + 2与 相同2 + 1。)

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

我正在寻找一种伪代码或任何相当流行且可读的语言的算法(注意那里的“和”......;),甚至只是关于如何实施这种搜索的英文描述。


编辑添加:

到目前为止提供了很多很好的信息。谢了,兄弟们!暂时总结一下:

  • 问题是 NP-Complete,所以要在合理的时间内获得 100% 的准确率,没有任何办法。
  • 该问题可以看作是子集和背包问题的变体。两者都有众所周知的启发式方法,它们可能适用于这个问题。

让想法不断涌现!再次感谢!

4

4 回答 4

5

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

阅读此处了解更多信息: http ://en.wikipedia.org/wiki/Subset_sum_problem

于 2010-07-05T10:55:51.300 回答
2

正如评论中所说的,数字范围仅从 1 到 30,这个问题有一个快速的解决方案。我在 C 中对其进行了测试,对于您给定的示例,它只需要几毫秒并且可以很好地扩展。复杂度为 O(n+k),其中 n 是 listA的长度,k 是 list 的长度B,但具有很高的常数因子(有 28.598 种可能得到从 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
于 2010-07-05T19:30:48.473 回答
1

听起来像一个背包问题(见http://en.wikipedia.org/wiki/Knapsack_problem。在那个页面上,他们还解释了这个问题通常是 NP 完全的。

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

于 2010-07-05T10:55:22.653 回答
1

这是子集和问题的一个小泛化。一般来说,它是 NP 完全的,但只要所有数字都是整数并且 in 中的最大数字B相对较小,我链接的维基百科文章中描述的伪多项式解决方案应该可以解决问题。

于 2010-07-05T11:03:32.990 回答