2

Как я могу вычислить N-го комбо, основанного только на его индексе. Должны быть (n + k-1)!/(K! (N-1)!) Комбинации с повторениями.Рассчитать N-ю мультимножественную комбинацию (с повторением), основанную только на индексе

with n=2, k=5 you get: 

0|{0,0,0,0,0} 
1|{0,0,0,0,1} 
2|{0,0,0,1,1} 
3|{0,0,1,1,1} 
4|{0,1,1,1,1} 
5|{1,1,1,1,1} 

Таким образом, black_magic_function (3) должен производить {0,0,1,1,1}.

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

with n=3, k=5 you get: 
i=0, {0,0,0,0,0} 
i=1, {0,0,0,0,1} 
i=2, {0,0,0,0,2} 
i=3, {0,0,0,1,1} 
i=4, {0,0,0,1,2} 
i=5, {0,0,0,2,2} 
i=6, {0,0,1,1,1} 
i=7, {0,0,1,1,2} 
i=8, {0,0,1,2,2} 
i=9, {0,0,2,2,2} 
i=10, {0,1,1,1,1} 
i=11, {0,1,1,1,2} 
i=12, {0,1,1,2,2} 
i=13, {0,1,2,2,2} 
i=14, {0,2,2,2,2} 
i=15, {1,1,1,1,1} 
i=16, {1,1,1,1,2} 
i=17, {1,1,1,2,2} 
i=18, {1,1,2,2,2} 
i=19, {1,2,2,2,2} 
i=20, {2,2,2,2,2} 

Алгоритм формирования его можно рассматривать как MBnext_multicombination в http://www.martinbroadhurst.com/combinatorial-algorithms.html

Update:

Так я думал, я бы заменить биномиальный коэффициент в Паскалях треугольнике с (n+k-1)!/(k!(n-1)!), чтобы увидеть, как это выглядит.

(* Mathematica code to display pascal and other triangle *) 
t1 = Table[Binomial[n, k], {n, 0, 8}, {k, 0, n}]; 
t2 = Table[(n + k - 1)!/(k! (n - 1)!), {n, 0, 8}, {k, 0, n}]; 
(*display*) 
{Row[#, "\t"]} & /@ t1 // Grid 
{Row[#, "\t"]} & /@ t2 // Grid 

Т1:

   1 
       1 1 
      1 2 1 
      1 3 3 1 
     1 4 6 4 1 
     1 5 10 10 5 1 
    1 6 15 20 15 6 1 
    1 7 21 35 35 21 7 1 
1 8 28 56 70 56 28 8 1 

Т2:

  Indeterminate 
       1 1 
      1 2 3 
      1 3 6 10 
     1 4 10 20 35 
     1 5 15 35 70 126 
    1 6 21 56 126 252 462 
    1 7 28 84 210 462 924 1716 
1 8 36 120 330 792 1716 3432 6435 

Сравнение с выходом n=3,k=5 консоли в начале этого поста: третий диагонали {3,6,10,15,21,28,36} дает индекс каждого опрокидывании точка {0,0,0,1,1} -> {0,0,1,1,1} -> {0,1,1,1,1} и т. д. Диагональ слева от нее, похоже, показывает, сколько значений содержится в предыдущем блоке (diagonal[2][i] == diagonal[3][i] - diagonal[3][i-1])). И если вы читаете пятый ряд пирамиды по горизонтали, вы получаете максимальное количество комбинаций для увеличения значений N в (n+k-1)!/(k!(n-1)!), где K=5.

Возможно, существует возможность использовать эту информацию для определения точной комбинации для произвольного индекса без перечисления всего набора, но я не уверен, что мне нужно зайти так далеко. Первоначальная проблема состояла в том, чтобы разложить полное комбо-пространство на равные подмножества, которые могут быть сгенерированы локально и параллельно работать с GPU. Таким образом, треугольник выше дает нам начальный индекс каждого блока, из которого комбо может быть тривиально выведено, и все его последующие элементы постепенно перечислимы. Он также дает нам размер блока и количество общих комбинаций. Итак, теперь проблема упаковки заключается в том, как размещать блоки с неравномерным размером в группы равной рабочей нагрузки по X количеству потоков.

+0

Каковы максимальные значения для n и k? –

+0

Максимальное значение K равно 6. Максимальное значение N не может превышать 300, но с некоторыми предварительными обработками можно свести куда-нибудь по линиям 20, 50 или 100. –

+0

Итак, случай n = 2 довольно тривиальна - просто придерживайтесь столько, сколько указано k, начиная с правой и оставляйте остальных на нуле. Для случая n = 3, k = 5, я получаю (3 + 5-1)!/5! (3-1)! = 21. Но я не совсем уверен, как выглядят комбинации. Не могли бы вы отредактировать свой вопрос и показать комбинации для n = 3 и k = 5? –

ответ

2

Смотрите пример на: https://en.wikipedia.org/wiki/Combinatorial_number_system#Finding_the_k-combination_for_a_given_number

Просто замените биномиальный коэффициент с (n+k-1)!/(k!(n-1)!).


Предполагая n=3,k=5, скажем, мы хотим вычислить 19-комбинацию (id=19).

id=0, {0,0,0,0,0} 
id=1, {0,0,0,0,1} 
id=2, {0,0,0,0,2} 
... 
id=16, {1,1,1,1,2} 
id=17, {1,1,1,2,2} 
id=18, {1,1,2,2,2} 
id=19, {1,2,2,2,2} 
id=20, {2,2,2,2,2} 

Результат, который мы ищем, - {1,2,2,2,2}.

Рассмотрение треугольника 'T2': n=3,k=5 указывает на 21, являясь пятым номером (сверху вниз) третьей диагонали (слева направо).

  Indeterminate 
       1 1 
       1 2 3 
      1 3 6 10 
      1 4 10 20 35 
     1 5 15 35 70 126 
     1 6 21 56 126 252 462 
    1 7 28 84 210 462 924 1716 
1 8 36 120 330 792 1716 3432 6435 

Нам нужно найти наибольшее число в этом ряду (по горизонтали, а не по диагонали), что не превышает нашу id=19 значение. Поэтому, двигаясь влево от 21, мы приходим к 6 (эта операция выполняется функцией largest ниже). Так как 6 является вторым номером в этой строке, он соответствует n==2 (или g[2,5] == 6 из кода ниже).

Теперь, когда мы нашли пятое число в комбинации, мы поднимаемся на пол в пирамиде, поэтому k-1=4. Мы также вычтем 6, с которым мы столкнулись ниже, от id, поэтому id=19-6=13. Повторяя весь процесс, мы находим 5 (n==2 еще раз) как наибольшее число, меньшее 13 в этой строке.

следующее: 13-5=8, наибольшее - 4 в этой строке (n==2 еще раз).

Следующий: 8-4=4, самый большой 3 в этой строке (n==2 еще раз).

Следующая: , Крупнейший в 1 в этом ряду (n==1)

Так собирая индексы на каждом этапе мы получаем {1,2,2,2,2}


Следующая Mathematica кода делает работу:

g[n_, k_] := (n + k - 1)!/(k! (n - 1)!) 

largest[i_, nn_, kk_] := With[ 
    {x = g[nn, kk]}, 
    If[x > i, largest[i, nn-1, kk], {nn,x}] 
] 

id2combo[id_, n_, 0] := {} 
id2combo[id_, n_, k_] := Module[ 
    {val, offset}, 
    {val, offset} = largest[id, n, k]; 
    Append[id2combo[id-offset, n, k-1], val] 
] 

Обновление: Порядок создания комбинаций MBnext_multicombination не соответствует id2combo, поэтому я не думаю, что они были лексикографическими. Функция ниже генерирует их в том же порядке, что и id2combo, и соответствует порядку функции Sort[] mathematica в списке списков.

void next_combo(unsigned int *ar, unsigned int n, unsigned int k) 
{ 
    unsigned int i, lowest_i; 

    for (i=lowest_i=0; i < k; ++i) 
     lowest_i = (ar[i] < ar[lowest_i]) ? i : lowest_i; 

    ++ar[lowest_i]; 

    i = (ar[lowest_i] >= n) 
     ? 0   // 0 -> all combinations have been exhausted, reset to first combination. 
     : lowest_i+1; // _ -> base incremented. digits to the right of it are now zero. 

    for (; i<k; ++i) 
     ar[i] = 0; 
} 
0

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

http://tablizingthebinomialcoeff.wordpress.com/

Я начал так же, пытаясь решить эту проблему. Я придумал следующий код, который использует один цикл для каждого значения k в формуле (n + k-1)!/K! (N-1)! при k = 5.Как написано, этот код будет генерировать все комбинации для случая п выбрать 5:

private static void GetCombos(int nElements) 
{ 
    // This code shows how to generate all the k-indexes or combinations for any number of elements when k = 5. 
    int k1, k2, k3, k4, k5; 
    int n = nElements; 
    int i = 0; 
    for (k5 = 0; k5 < n; k5++) 
    { 
     for (k4 = k5; k4 < n; k4++) 
     { 
     for (k3 = k4; k3 < n; k3++) 
     { 
      for (k2 = k3; k2 < n; k2++) 
      { 
       for (k1 = k2; k1 < n; k1++) 
       { 
        Console.WriteLine("i = " + i.ToString() + ", " + k5.ToString() + " " + k4.ToString() + 
        " " + k3.ToString() + " " + k2.ToString() + " " + k1.ToString() + " "); 
        i++; 
       } 
      } 
     } 
     } 
    } 
} 

Выход из этого метода:

i = 0, 0 0 0 0 0 
i = 1, 0 0 0 0 1 
i = 2, 0 0 0 0 2 
i = 3, 0 0 0 1 1 
i = 4, 0 0 0 1 2 
i = 5, 0 0 0 2 2 
i = 6, 0 0 1 1 1 
i = 7, 0 0 1 1 2 
i = 8, 0 0 1 2 2 
i = 9, 0 0 2 2 2 
i = 10, 0 1 1 1 1 
i = 11, 0 1 1 1 2 
i = 12, 0 1 1 2 2 
i = 13, 0 1 2 2 2 
i = 14, 0 2 2 2 2 
i = 15, 1 1 1 1 1 
i = 16, 1 1 1 1 2 
i = 17, 1 1 1 2 2 
i = 18, 1 1 2 2 2 
i = 19, 1 2 2 2 2 
i = 20, 2 2 2 2 2 

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

Я написал это на C#, но вы можете использовать его с другими языками, такими как C/C++, Java или Python, без слишком большого количества изменений.

Одной из идей для несколько неэффективного решения является изменение GetCombos для принятия k как ввода. Так как k ограничено 6, тогда можно было бы поставить тест на k. Таким образом, код для генерации всех возможных комбинаций для п выбрать K случае будет выглядеть следующим образом:

private static void GetCombos(int k, int nElements) 
{ 
    // This code shows how to generate all the k-indexes or combinations for any n choose k, where k <= 6. 
    // 
    int k1, k2, k3, k4, k5, k6; 
    int n = nElements; 
    int i = 0; 
    if (k == 6) 
    { 
     for (k6 = 0; k6 < n; k6++) 
     { 
     for (k5 = 0; k5 < n; k5++) 
     { 
      for (k4 = k5; k4 < n; k4++) 
      { 
       for (k3 = k4; k3 < n; k3++) 
       { 
        for (k2 = k3; k2 < n; k2++) 
        { 
        for (k1 = k2; k1 < n; k1++) 
        { 
         Console.WriteLine("i = " + i.ToString() + ", " + k5.ToString() + " " + k4.ToString() + 
          " " + k3.ToString() + " " + k2.ToString() + " " + k1.ToString() + " "); 
         i++; 
        } 
        } 
       } 
      } 
     } 
     } 
    } 
    else if (k == 5) 
    { 
     for (k5 = 0; k5 < n; k5++) 
     { 
     for (k4 = k5; k4 < n; k4++) 
     { 
      for (k3 = k4; k3 < n; k3++) 
      { 
       for (k2 = k3; k2 < n; k2++) 
       { 
        for (k1 = k2; k1 < n; k1++) 
        { 
        Console.WriteLine("i = " + i.ToString() + ", " + k5.ToString() + " " + k4.ToString() + 
         " " + k3.ToString() + " " + k2.ToString() + " " + k1.ToString() + " "); 
        i++; 
        } 
       } 
      } 
     } 
     } 
    } 
    else if (k == 4) 
    { 
     // One less loop than k = 5. 
    } 
    else if (k == 3) 
    { 
     // One less loop than k = 4. 
    } 
    else if (k == 2) 
    { 
     // One less loop than k = 3. 
    } 
    else 
    { 
     // k = 1 - error? 
    } 
} 

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

private static int[] GetComboOfRank(int k, int nElements, int Rank) 
{ 
    // Gets the combination for the rank using the formula (n+k-1)!/k!(n-1)! where k <= 6. 
    int k1, k2, k3, k4, k5, k6; 
    int n = nElements; 
    int i = 0; 
    int[] ReturnArray = new int[k]; 
    if (k == 6) 
    { 
     for (k6 = 0; k6 < n; k6++) 
     { 
     for (k5 = 0; k5 < n; k5++) 
     { 
      for (k4 = k5; k4 < n; k4++) 
      { 
       for (k3 = k4; k3 < n; k3++) 
       { 
        for (k2 = k3; k2 < n; k2++) 
        { 
        for (k1 = k2; k1 < n; k1++) 
        { 
         if (i == Rank) 
         { 
          ReturnArray[0] = k1; 
          ReturnArray[1] = k2; 
          ReturnArray[2] = k3; 
          ReturnArray[3] = k4; 
          ReturnArray[4] = k5; 
          ReturnArray[5] = k6; 
          return ReturnArray; 
         } 
         i++; 
        } 
        } 
       } 
      } 
     } 
     } 
    } 
    else if (k == 5) 
    { 
     for (k5 = 0; k5 < n; k5++) 
     { 
     for (k4 = k5; k4 < n; k4++) 
     { 
      for (k3 = k4; k3 < n; k3++) 
      { 
       for (k2 = k3; k2 < n; k2++) 
       { 
        for (k1 = k2; k1 < n; k1++) 
        { 
        if (i == Rank) 
        { 
         ReturnArray[0] = k1; 
         ReturnArray[1] = k2; 
         ReturnArray[2] = k3; 
         ReturnArray[3] = k4; 
         ReturnArray[4] = k5; 
         return ReturnArray; 
        } 
        i++; 
        } 
       } 
      } 
     } 
     } 
    } 
    else if (k == 4) 
    { 
     // Same code as in the other cases, but with one less loop than k = 5. 
    } 
    else if (k == 3) 
    { 
     // Same code as in the other cases, but with one less loop than k = 4. 
    } 
    else if (k == 2) 
    { 
     // Same code as in the other cases, but with one less loop than k = 3. 
    } 
    else 
    { 
     // k = 1 - error? 
    } 
    // Should not ever get here. If we do - it is some sort of error. 
    throw ("GetComboOfRank - did not find rank"); 
} 

ReturnArray возвращает комбинацию, связанную с рангом. Таким образом, этот код должен работать на вас. Однако он будет намного медленнее, чем можно было бы сделать, если бы был выполнен поиск таблицы. Проблема с 300 выбирает 6:

300 choose 6 = 305!/(6!(299!) = 305*304*303*302*301*300/6! = 1,064,089,721,800 

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

20 choose 6 = 25!/(6!(19!)) = 25*24*23*22*21*20/6! = 177,100 
20 choose 5 = 24!/(5!(19!)) = 24*23*22*21,20/5! = 42,504 
20 choose 4 = 23!/(4!(19!)) = 23*22*21*20/4!  = 8,855 
20 choose 3 = 22!/(3!(19!)) = 22*21*20/3!   = 1,540 
20 choose 2 = 21!/(2!(19!)) = 22*21/2!    =  231 
                 ======= 
                 230,230 

Если один байт используется для каждого значения комбинации, то общее число байтов, используемых для хранить таблицу (с помощью зубчатого массива или, возможно, 5 отдельных таблиц) в памяти может быть вычислена как:

177,100 * 6 = 1,062,600 
42,504 * 5 = 212,520 
    8,855 * 4 = 35,420 
    1,540 * 3 =  4,620 
    231 * 2 =  462 
       ========= 
       1,315,622 

это зависит от целевой машины и сколько памяти доступно, но 1,315,622 байт не так много памяти, когда сегодня у многих машин имеется гигабайт памяти.

 Смежные вопросы

  • Нет связанных вопросов^_^