在一个集合中查找与另一个集合中的数字相加的一组数字

Posted

技术标签:

【中文标题】在一个集合中查找与另一个集合中的数字相加的一组数字【英文标题】:Find set of numbers in one collection that adds up to a number in another 【发布时间】:2011-03-11 20:49:23 【问题描述】:

对于我正在制作的游戏,我有一个数字列表——比如说 [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 sum 或knapsack 问题的变体。两者都有众所周知的启发式方法,它们可能适用于这个问题。

让创意不断涌现!再次感谢!

【问题讨论】:

是否有元素或数字大小的上限?如果您保持较低的值,则无需等待太久即可计算出来。 如果可以利用某些约束,应该可以使用一些启发式方法。例如,数组 A 和 B 的大小和/或成员是否恒定?或者你可能遇到的数字范围有限? @tathagata:数字永远不会超过 30 也不会低于 1。那将是一个限制。 @Just ...: 数字是 1 到 30 之间的整数?这是关于A中的数字吗?我们可以假设 A 中的数字是不同的吗? @Just... B 中的数字是否也在 1 到 30 之间?如果是这样,考虑到大量但合理的空间,问题是易于处理的。 【参考方案1】:

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

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

【讨论】:

我偷偷怀疑它是 NP-hard 或更糟。我可以应用他们的启发式方法吗? 是的,当然。首先,***文章中有一个近似算法,看看它是否符合您的需求。其次,您可以应用分支定界方法(或 alpha-beta 修剪)。如果您对列表中的最大数字有一个上限,您还可以应用一些启发式方法,例如为 B 中的数字 t 编译所有可能的添加剂的列表。(只有当 t,B 是相对较小,A 很大...)【参考方案2】:

正如在数字范围仅为 1 到 30 的 cmets 中所说,这个问题有一个快速的解决方案。我在 C 中对其进行了测试,对于您给定的示例,它只需要几毫秒并且可以很好地扩展。复杂度为 O(n+k),其中 n 是列表 A 的长度,k 是列表 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

【讨论】:

【参考方案3】:

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

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

【讨论】:

嗯,这就是为什么我还询问了可以在合理时间内给我“足够好”结果的启发式方法。【参考方案4】:

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

【讨论】:

以上是关于在一个集合中查找与另一个集合中的数字相加的一组数字的主要内容,如果未能解决你的问题,请参考以下文章

从一组中找出产生最少浪费的数字

如何使用 LINQ 从一组数字中查找 n 项的所有组合?

只有在字符串中找到一组数字时,C# 正则表达式才匹配

oracle集合

找到一组排列,有一个约束

您如何识别文本中的一组 2 或 3 位数字? postgresql