Количество комбинаций с повторениями

у меня очень неэффективный способ подсчета комбинаций N/2 элементы из массива размера N. Что я делаю, так это сортирую массив для начала, а затем перебираю перестановки массива, создавая мультисети с половиной элементов и вставляя их в набор. Наконец-то я подсчитал количество съемок.

long GetCombinations(std::vector<double> nums) {
    long combinations = 0;
    std::sort(nums.begin(), nums.end());
    std::set<std::multiset<double>> super_set;

    do {
        std::multiset<double> multi_set;

        for (unsigned int i = 0; i < nums.size() / 2; ++i)
            multi_set.insert(nums[i]);

        auto el = (super_set.insert(multi_set));

        if (el.second)
            ++combinations;

    } while (std::next_permutation(nums.begin(), nums.end()));

    return combinations;
}

код работает, но он очень неэффективен. Для данного массива [0.5, 0.5, 1, 1] есть 3 комбинации размера 2:

0.5, 0.5
1, 1
1, 0.5

есть ли другой алгоритм или подход, который может увеличить скорость этого кода?

1 ответов


Подсчет Комбинаций

вообще говоря, определение количества комбинаций определенного множества довольно тривиально. Однако распространение этого на мультисет, где каждый элемент повторяется определенное количество раз, значительно сложнее и не так хорошо документировано. @WorldSEnder связан с ответом math/stackexchange, который имеет комментарий со ссылкой на эту замечательную статью в комбинаторике называется Комбинаторные Фрэнк Раски. Если вы перейдете на страницу 71, есть раздел, который рассматривает эту тему с большей строгостью.

Основные понятия

  1. Set-коллекция distinct объекты. - Е. Г. {a, b} это то же самое, что {a, a, b} и оба имеют мощность 2
  2. Multiset-аналогично набору, но позволяет дублировать записи. - Е. Г. {a, b} и {a, a, b} разные мультимножеств с кардинальностью 2 и 3 соответственно
  3. биномиальный коэффициент-дает число k-подмножества элементов n-элементного множества.
  4. коэффициент Мультимножества/количество - число мультимножеств мощности k с элементами, взятыми из конечного множества.

заблуждения

существует мнение, что существует простая формула, которая быстро вычислит количество комбинаций для нескольких наборов длины k где каждый элемент повторяется определенное количество раз (см. комментарии с большим количеством голосов выше). Ниже мы рассмотрим каждый из известных методов.

начнем с общего применения биномиального коэффициента. Мы сразу видим, что это не удастся, поскольку это строго предназначено для вычисления количества комбинаций a set, где повторяющиеся записи не допускаются. В нашем случае допускаются дубликаты.

чтение далее на странице Википедии есть раздел под названием количество комбинаций с повторением. Это выглядит многообещающим, как у нас есть некоторые репликация. Мы также видим модифицированный биномиальный коэффициент, который кажется еще более перспективным. Более пристальный взгляд показывает, что это тоже не удастся, так как это строго относится к мультимножеств, где каждый элемент повторяется до k раза.

наконец, мы пробуем коэффициент multiset. Один из приведенных примеров очень похож на то, что мы пытаемся сделать.

"сначала рассмотрим обозначения для мультисетей, которые будут представлять {a, a, a, a, a, A, b, b, c, c, C, d, d, d, d, D, D} (6 as, 2 bs, 3 cs, 7 ds) в следующем виде:"

это выглядит как хороший кандидат для того, что мы пытаемся получить. Тем не менее, вы увидите, что они продолжают выводить количество способов построения мультисета мощности 18 из набора из 4 различных элементы. Это эквивалентно числу целое число композиций 18 длины 4. Е. Г.

18 + 0 + 0 + 0
17 + 1 + 0 + 0
16 + 2 + 0 + 0
       .
       .
       .
5 +  4 + 6 + 3
4 +  5 + 6 + 3
3 +  6 + 6 + 3
       .
       .
       .
0 +  1 + 0 + 17
0 +  0 + 1 + 17
0 +  0 + 0 + 18

как вы можете видеть, порядок имеет значение с композициями, которые явно не относятся к нашей ситуации.

последние два упомянутых метода получены из известного звезды и полосы метод для простых задач учета. Насколько я могу судить, этот метод не может быть легко распространен на наш случай.

A Рабочий Алгоритм

unsigned long int getCombinationCount(std::vector<double> nums) {

    unsigned long int n = nums.size();
    unsigned long int n2 = n / 2;
    unsigned long int numUnique = 1;
    unsigned long int numCombinations;

    std::sort(nums.begin(), nums.end());
    std::vector<int> numReps;

    double testVal = nums[0];
    numReps.push_back(1);

    for (std::size_t i = 1; i < n; ++i) {
        if (nums[i] != testVal) {
            numReps.push_back(1);
            testVal = nums[i];
            ++numUnique;
        } else {
            ++numReps[numUnique - 1];
        }
    }

    int myMax, r = n2 + 1;
    std::vector<double> triangleVec(r);
    std::vector<double> temp(r);
    double tempSum;

    myMax = r;
    if (myMax > numReps[0] + 1)
        myMax = numReps[0] + 1;

    for (int i = 0; i < myMax; ++i)
        triangleVec[i] = 1;

    temp = triangleVec;

    for (std::size_t k = 1; k < numUnique; ++k) {
        for (int i = n2; i > 0; --i) {
            myMax = i - numReps[k];
            if (myMax < 0)
                myMax = 0;

            tempSum = 0;
            for (int j = myMax; j <= i; ++j)
                tempSum += triangleVec[j];

            temp[i] = tempSum;
        }
        triangleVec = temp;
    }

    numCombinations = (unsigned long int) triangleVec[n2];

    return numCombinations;
}

объяснение с использованием модифицированного треугольника Паскаля

записи в традиционном треугольник Паскаля (PT отсюда и далее) представляют биномиальный коэффициент, где строка треугольника - это количество элементов в вашем наборе, а столбец-длина комбинаций, которые вы хотите создать. Построение треугольника является ключом к тому, как мы будем атаковать проблему.

если вы заметите, что для традиционный PT, чтобы получить определенную запись, скажем (i, j) здесь я это строка и j это столбец, вы должны добавить записи (i - 1, j - 1) и (i - 1, j). Вот пример.

                  1
                1   1
              1   2   1            N.B. The first 10 is in the 5th row and 3rd column
            1   3   3   1               and is obtained by adding the entries from the
          1   4   6   4   1             4th row and 2nd/3rd.
        1   5   10  10  5   1
      1   6   15  20  15  6   1

мы можем расширить это до общего multiset, где каждый элемент повторяется определенное количество раз. Рассмотрим несколько примеров.

Пример 1: v1 = {1, 2, 2}, v2 = {1, 2, 2, 3, 3, 3}, и v3 = {1,2,2,3,3,3,4,4,4,4}

ниже мы имеем все возможные комбинации v1 choose 1 - 3 а также v2 choose 1 - 6.

     [,1]                    [,1]
[1,]    1               [1,]    1
[2,]    2               [2,]    2
                        [3,]    3

     [,1] [,2]               [,1] [,2]
[1,]    1    2          [1,]    1    2
[2,]    2    2          [2,]    1    3
                        [3,]    2    2
                        [4,]    2    3
                        [5,]    3    3

     [,1] [,2] [,3]          [,1] [,2] [,3]
[1,]    1    2    2     [1,]    1    2    2
                        [2,]    1    2    3
                        [3,]    1    3    3
                        [4,]    2    2    3
                        [5,]    2    3    3
                        [6,]    3    3    3

                             [,1] [,2] [,3] [,4]
                        [1,]    1    2    2    3
                        [2,]    1    2    3    3
                        [3,]    1    3    3    3
                        [4,]    2    2    3    3
                        [5,]    2    3    3    3

                             [,1] [,2] [,3] [,4] [,5]
                        [1,]    1    2    2    3    3
                        [2,]    1    2    3    3    3
                        [3,]    2    2    3    3    3

                             [,1] [,2] [,3] [,4] [,5] [,6]
                        [1,]    1    2    2    3    3    3

запишем количество комбинаций для всех k как v1 и v2.

2  2  1
3  5  6  5  3  1

я собираюсь дать вам количество комбинаций для всех k of v3 (я оставлю это читателю, чтобы перечислить их).

4  9 15 20 22 20 15  9  4  1

мы объединяем результаты выше особым образом и обратите внимание, что вещи начинают казаться очень знакомыми.

         2  2  1
     3   5   6   5  3  1
4  9  15  20  22  20  15  9  4  1

мы добавляем несколько из них в качестве держателей места для завершения этого измененного PT

                1   1
            1   2   2   1
      1   3   5   6   5   3   1
1  4  9  15  20  22  20  15   9  4  1

что это значит? Несколько ясно, что числа в каждой последующей строке представляют собой комбинацию чисел в предыдущей строке. Но как?....

мы позволяем частоте каждого элемента направлять нас.

например, чтобы получить третью строку, представляющую число комбинации v2 choose 1 - 6 (игнорируя первый 1), мы смотрим на строку 2. Поскольку частота 3-го элемента равна 3, мы добавляем 4 элемента (3 + 1.. так же, как с биномиальными коэффициентами для нахождения количества комбинаций множеств с различными элементами, мы добавляем 2 записи вместе или 1 + 1) в строке выше со столбцом, меньшим или равным столбцу, который мы находим. Итак, мы имеем:

if the column index is non-positive or greater than the 
number of columns in the previous row, the value is 0

    v2 choose 3
(3, 2) =  (2, 2 - 3) + (2, 2 - 2) + (2, 2 - 1) + (2, 2 - 0)
       =       0     +      0     +      1     +    2 
       =   3

v2 choose 4           
(3, 3) =  (2, 3 - 3) + (2, 3 - 2) + (2, 3 - 1) + (2, 3 - 0)
       =       0     +      1     +      2     +    2 
       =   5           

v2 choose 5 
(3, 4) =  (2, 4 - 3) + (2, 4 - 2) + (2, 4 - 1) + (2, 4 - 0)
       =       1     +      2     +      2     +    1 
       =   6

v2 choose 6                                   outside of range
(3, 5) =  (2, 5 - 3) + (2, 5 - 2) + (2, 5 - 1) + (2, 5 - 0)
       =       2     +      2     +      1     +    0 
       =   5

       etc.

продолжая эту логику, давайте посмотрим, сможем ли мы получить число k-комбинаций v3. Так как частота 4-й элемент 4, нам нужно будет добавить 5 записей.

v3 choose 3
(4, 2) =  (3, 2 - 4) + (3, 2 - 3) + (3, 2 - 2) + (3, 2 - 1) + (3, 2 - 0)
       =       0     +      0     +     0      +      1     +     3 
       =   4

v3 choose 4 
(4, 3) =  (3, 3 - 4) + (3, 3 - 3) + (3, 3 - 2) + (3, 3 - 1) + (3, 3 - 0)
       =       0     +      0     +      1     +    3       +     5
       =   9           

v3 choose 5  
(4, 4) =  (3, 4 - 4) + (3, 4 - 3) + (3, 4 - 2) + (3, 4 - 1) + (3, 4 - 0)
       =       0     +     1      +      3     +     5      +     6
       =   15

v3 choose 6
(4, 5) =  (3, 5 - 4) + (3, 5 - 3) + (3, 5 - 2) + (3, 5 - 1) + (3, 5 - 0)
       =       1     +     3      +      5     +       6    +    5
       =   20

       etc.

и действительно, мы получаем правильное число k-комбинаций v3.

Пример 2: z1 = {1,1,1,2}, z2 = {1,1,1,1,2,3,3,3,3,3} и z3 = {1,1,1,1,2,3,3,3,3,3,4,4}

вы заметите, что мы строим эти векторы таким образом, что каждый последующий вектор содержит предыдущие векторы. Мы делаем это для того, чтобы мы могли правильно построить наш модифицированный PT. Это аналогично традиционному PT, Где с каждой последующей строкой мы просто добавляем одно число к предыдущей строке. Модифицированный PT для этих векторов:

                1   1   1  1
             1   2   2   2   1
      1  3  5  7   8   8   7   5   3  1
  1  4   9  15  20  23   23  20  15  9  4  1

давайте построим z2 choose 6 и z3 choose 9 чтобы убедиться, что мы правы:

 z2 choose 6
      [,1] [,2] [,3] [,4] [,5] [,6]
 [1,]    1    1    1    2    3    3
 [2,]    1    1    1    3    3    3      This shows that we produce 7 combs
 [3,]    1    1    2    3    3    3      just as predicted by our modified
 [4,]    1    1    3    3    3    3      PT (i.e. entry (3, 6 + 1) = 7)
 [5,]    1    2    3    3    3    3
 [6,]    1    3    3    3    3    3
 [7,]    2    3    3    3    3    3


 z3 choose 9
     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
[1,]    1    1    1    2    3    3    3    3    3
[2,]    1    1    1    2    3    3    3    3    4
[3,]    1    1    1    2    3    3    3    4    4  This shows that we produce 9 
[4,]    1    1    1    3    3    3    3    3    4  combs just as predicted by 
[5,]    1    1    1    3    3    3    3    4    4  our modified PT (i.e. entry
[6,]    1    1    2    3    3    3    3    3    4  (4, 9 + 1) = 9)
[7,]    1    1    2    3    3    3    3    4    4
[8,]    1    1    3    3    3    3    3    4    4
[9,]    1    2    3    3    3    3    3    4    4

так же, как краткое Примечание, первая строка места, удерживающего те аналогична второй строке традиционного PT (т. е. 1 1). Грубо говоря (см. код для edge случаях), если первый элемент имеет частоту m, первая строка измененного PT будет содержать m + 1 те.

причина, по которой нет общей формулы (например, что-то похожее на биномиальный коэффициент)

как вы можете видеть из приведенных выше 2 примеров, модифицированные PT основаны на определенных мультисетях и, следовательно, не могут быть обобщены. Даже если вы рассматривали мультисети определенной мощности, состоящие из одних и тех же различных элементов, модифицированные PT будут отличаться. Например, multiset a = {1, 2, 2, 3, 3, 3} и b = {1, 1, 2, 2, 3, 3} создайте следующие измененные PT соответственно:

     1 1
   1 2 2 1
1 3 5 6 5 3 1

    1 1 1
  1 2 3 2 1
1 3 6 7 6 3 1

обратите внимание, что a choose 2 = 5, тогда как b choose 2 = 6.

критерии:

вот ссылка ideone демонстрация ускорения нового алгоритма. Для вектора {4, 2, 6, 4, 9, 8, 2, 4, 1, 1, 6, 9}, время для оригинала было 2285718 часы тикают, тогда как алгоритм выше завершен в 8 часы тикают в общей сложности ускорение 2285728 / 8 = 285714.75... в сто тысяч раз быстрее. Они оба возвращают одинаковое количество комбинаций (т. е. 122). Большая часть прироста скорости происходит от избежания явного генерирования любых комбинаций (или перестановок, как это делает код OP).