Быстрая индексация k-комбинаций

12

Я возвращаюсь к старой проблеме, над которой я работал некоторое время назад.

Типичный сценарий: «3 бита устанавливаются в 8-битном целом числе», т.е. 00000111.

Все уникальные комбинации с 3 установленными битами могут быть легко созданы (по порядку) с помощью вложенных циклов. Меня интересует комбинация индекса отображения <->, т. Е. «00001011» будет второй комбинацией (или значением «1» в индексе, начинающемся с нуля).

До сих пор я просматривал все комбинации и сохранял их в таблице, делая поиск индекса -> разговор операцией O (1). Другое направление - O (ln (n)) с двойным поиском.

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

Какой простой способ рассчитать n-ю комбинацию или индекс для данной комбинации? Порядок комбинаций был бы неплох, но не обязателен.

Эйко
источник
@MichaelT Ваши ссылки не обращаются к вопросу - итерации по комбинациям не проблема. Это касается отображения индексов и комбинаций. Учитывая "11001000", каков индекс (или счетчик перечисления, если хотите)? Какой код принадлежит индексу 1673?
Эйко
1
Ааа, в этом случае вы можете найти OEIS полезным. Например, последовательность 3,5,6,9,10,12,17,18 дает нам сумму двух различных степеней двойки, что является еще одним способом сказать «два бита в» на математическом жаргоне. Различные формулы показывают различные способы вычисления n-го значения.
1
8-битные целые числа имеют только 256 комбинаций любых битовых шаблонов, которые тривиально хранить (и занимают меньше места, чем любой умный код). Каковы ваши целевые / расчетные биты?
9000
1
Как выяснилось в другом месте, это известно как комбинаторная система счисления , и Хак Госпера может сделать это в O (1). Логика была сделана в HACKMEM 175 и объяснена в этом сообщении в блоге ( оригинал довольно краткий).

Ответы:

4

Генерирование n-й комбинации называется алгоритмом «unranking». Обратите внимание, что перестановки и комбинации часто можно приравнять к параметризации проблемы. Не зная точно, в чем заключается проблема, трудно порекомендовать точный правильный подход, и на самом деле для большинства комбинаторных задач обычно существует несколько различных алгоритмов ранжирования / исключения.

Один хороший ресурс - «Комбинаторные алгоритмы» Креера и Стинсона. В этой книге есть много хороших алгоритмов ранжирования и однозначных объяснений. Есть более продвинутые ресурсы, но я бы рекомендовал Kreher в качестве отправной точки. В качестве примера алгоритма отмены рейтинга рассмотрим следующее:

/** PKSUL : permutation given its rank, the slots and the total number of items
 *  A combinatorial ranking is number of the permutation when sorted in lexicographical order
 *  Example:  given the set { 1, 2, 3, 4 } the ctItems is 4, if the slot count is 3 we have:
 *     1: 123    7: 213   13: 312   19: 412
 *     2: 124    8: 214   14: 314   20: 413
 *     3: 132    9: 231   15: 321   21: 421
 *     4: 134   10: 234   16: 324   22: 423
 *     5: 142   11: 241   17: 341   23: 431
 *     6: 143   12: 243   18: 342   24: 432
 *  From this we can see that the rank of { 2, 4, 1 } is 11, for example. To unrank the value of 11:
 *       unrank( 11 ) = { 11 % (slots - digit_place)!, unrank( remainder ) }
 * @param rank           the one-based rank of the permutation
 * @param ctItems        the total number of items in the set
 * @param ctSlots        the number of slots into which the permuations are generated
 * @param zOneBased      whether the permutation array is one-based or zero-based
 * @return               zero- or one-based array containing the permutation out of the set { ctItems, 1,...,ctItems }
 */
public static int[] pksul( final int rank, final int ctItems, final int ctSlots, boolean zOneBased ){
    if( ctSlots <= 0 || ctItems <= 0 || rank <= 0 ) return null;
    long iFactorial = factorial_long( ctItems - 1 ) / factorial_long( ctItems - ctSlots );
    int lenPermutation = zOneBased ? ctSlots + 1 : ctSlots;
    int[] permutation = new int[ lenPermutation ];
    int[] listItemsRemaining = new int[ ctItems + 1 ];
    for( int xItem = 1; xItem <= ctItems; xItem++ ) listItemsRemaining[xItem] = xItem; 
    int iRemainder = rank - 1;
    int xSlot = 1;
    while( true ){
        int iOrder = (int)( iRemainder / iFactorial ) + 1;
        iRemainder = (int)( iRemainder % iFactorial );
        int iPlaceValue = listItemsRemaining[ iOrder ];
        if( zOneBased ){
            permutation[xSlot] = iPlaceValue;
        } else {
            permutation[xSlot - 1] = iPlaceValue;
        }
        for( int xItem = iOrder; xItem < ctItems; xItem++ ) listItemsRemaining[xItem] = listItemsRemaining[xItem + 1]; // shift remaining items to the left
        if( xSlot == ctSlots ) break;
        iFactorial /= ( ctItems - xSlot );
        xSlot++;
    }
    if( zOneBased ) permutation[0] = ctSlots;
    return permutation;
}

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

Тайлер Дурден
источник