Швидке індексування 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-ї комбінації називається алгоритмом "відключення". Зауважте, що перестановки та комбінації часто можна прирівнювати за способом параметризації проблеми. Не знаючи точно, в чому полягає проблема, важко рекомендувати точний правильний підхід, і насправді для більшості комбінаторних проблем зазвичай можливе кілька різних алгоритмів ранжирування / відключення.

Один хороший ресурс - «Комбінаторні алгоритми» Крегера та Стінсона. У цій книзі багато чітко пояснених алгоритмів ранжирування та нерозв'язування. Є більш досконалі ресурси, але я б рекомендував Крехер як вихідну точку. Як приклад алгоритму розв’язування розглянемо наступне:

/** 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;
}

Це розстановка перестановки, але, як згадувалося вище, у багатьох випадках ви можете перетворити комбінацію відкручування в еквівалентну задачу перестановки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.