Найти набор чисел в одном наборе, который в сумме дает число в другом

Для игры, которую я делаю, у меня есть ситуация, когда у меня есть список чисел, например [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% точности в разумные сроки не обойтись без грубой силы.
  • Задачу можно рассматривать как вариант либо суммы подмножества, либо проблемы с рюкзаком. Для обоих существуют хорошо известные эвристики, которые могут быть адаптированы к этой проблеме.

Держите идеи! И еще раз спасибо!


person JUST MY correct OPINION    schedule 05.07.2010    source источник
comment
Есть ли верхний предел элементов или размер числа? Если вы держите taht на низком уровне, его можно рассчитать, не ожидая слишком долго.   -  person Thariama    schedule 05.07.2010
comment
Должна быть возможность использовать некоторую эвристику, если есть определенные ограничения, которые можно использовать. Например, является ли размер и/или члены любого из массивов A и B постоянными? Или, может быть, диапазон числа, с которым вы, вероятно, столкнетесь, ограничен?   -  person tathagata    schedule 05.07.2010
comment
@tathagata: число никогда не превысит 30 и не опустится ниже 1. Это будет одним из ограничений.   -  person JUST MY correct OPINION    schedule 05.07.2010
comment
@Just ...: числа представляют собой целые числа от 1 до 30? Это из-за чисел в А? Можем ли мы предположить, что числа в A различны?   -  person    schedule 05.07.2010
comment
Если да, то проблема не сложная. Вы можете создать список со всеми комбинациями для чисел от 1 до 30 и какие числа необходимы для каждой комбинации. Затем вы создаете список со всеми числами, которые встречаются в списке A. Наконец, вы проверяете для каждой комбинации, есть ли нужные числа в occurs-in-A-list.   -  person rudi-moore    schedule 05.07.2010
comment
@Just... Числа в B тоже от 1 до 30? Если да, то проблема решаема, учитывая изрядное, но разумное пространство.   -  person Il-Bhima    schedule 05.07.2010
comment
@ Иль-Бхима: Да. Все числа, участвующие во всех таких сравнениях, будут от 1 до 30. Пространство в значительной степени не будет проблемой.   -  person JUST MY correct OPINION    schedule 05.07.2010
comment
@Moron: Нет, мы не можем предполагать, что числа различны. Вполне возможно иметь список, состоящий только из единиц.   -  person JUST MY correct OPINION    schedule 05.07.2010


Ответы (4)


Эта задача является NP-полной... Это некоторая вариация задачи о сумме подмножества, которая, как известно, является NP-полной (на самом деле, задача о сумме подмножества проще, чем ваша).

Подробнее читайте здесь: http://en.wikipedia.org/wiki/Subset_sum_problem

person Protostome    schedule 05.07.2010
comment
У меня было подозрение, что это было NP-сложно или хуже. Могу ли я применить их эвристику? - person JUST MY correct OPINION; 05.07.2010
comment
Ну конечно; естественно. Во-первых, в статье в википедии есть приблизительный алгоритм, посмотрите, соответствует ли он вашим потребностям. Во-вторых, вы можете применить методологию ветвей и границ (или сокращение альфа-бета). Если у вас есть верхняя граница наибольшего числа в вашем списке, вы также можете применить некоторые эвристики, такие как составление списка всех возможных добавок к числу t в B. (Это может иметь смысл только в том случае, если t, B относительно мала, а A огромна...) - person Protostome; 05.07.2010

Как сказано в комментариях с числами только от 1 до 30, проблема имеет быстрое решение. Я протестировал его на 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
person rudi-moore    schedule 05.07.2010

Похоже на проблему с ранцем (см. http://en.wikipedia.org/wiki/Knapsack_problem). На той же странице объясняют, что задача вообще NP-полная.

Я думаю, это означает, что если вы хотите найти ВСЕ допустимые комбинации, вам просто нужно много времени.

person Patrick    schedule 05.07.2010
comment
Вот почему я также спросил об эвристике, которая могла бы дать мне достаточно хорошие результаты в разумные сроки. - person JUST MY correct OPINION; 05.07.2010

Это небольшое обобщение проблемы суммы подмножеств. В общем, это NP-полное, но пока все числа являются целыми числами, а максимальное число в B относительно невелико, псевдополиномиальное решение, описанное в статье Википедии, на которую я ссылаюсь, должно сработать.

person svick    schedule 05.07.2010