случайная перестановка - PullRequest
9 голосов
/ 20 июня 2010

Я хотел бы создать случайную перестановку как можно быстрее.Проблема: перемешивание Кнута, которое является O (n), включает генерирование n случайных чисел.Поскольку генерация случайных чисел довольно дорогая.Я хотел бы найти функцию O (n), включающую фиксированное количество случайных чисел O (1).

Я понимаю, что этот вопрос задавался ранее, но я не видел соответствующих ответов.

Просто чтобы подчеркнуть: я не ищу ничего, кроме O (n), просто алгоритм, включающий меньшее количество генераций случайных чисел.

Спасибо

Ответы [ 8 ]

8 голосов
/ 20 июня 2010

Создайте отображение 1-1 каждой перестановки в число от 1 до n! (n факториал). Создайте случайное число от 1 до n !, используйте отображение, получите перестановку.

Для сопоставления, возможно, это будет полезно: http://en.wikipedia.org/wiki/Permutation#Numbering_permutations

Конечно, это быстро вышло бы из-под контроля, как n! может стать очень большим в ближайшее время.

2 голосов
/ 20 июня 2010

Генерация случайного числа занимает много времени, говорите вы?Реализация Javas Random.nextInt примерно равна

oldseed = seed;
nextseed = (oldseed * multiplier + addend) & mask;
return (int)(nextseed >>> (48 - bits));

Это слишком много работы для каждого элемента?

1 голос
/ 10 июля 2012

Вы уверены, что ваш математический и алгоритмический подход к проблеме верен?

Я столкнулся с точно такой же проблемой, когда случайный случай с Фишером-Йейтсом будет узким местом в угловых случаях. Но для меня настоящая проблема - алгоритм грубой силы, который не подходит для всех задач. Следующая история объясняет проблему и оптимизации, которые я до сих пор предлагал.

Карты для 4 игроков

Количество возможных сделок: 96 бит номер. Это создает большую нагрузку для генератора случайных чисел, чтобы избежать статических аномалий при выборе игрового плана из сгенерированного примера набора сделок. Я решил использовать 2xmt19937_64, отобранный из / dev / random, из-за длительного периода и интенсивной рекламы в сети, что это хорошо для научного моделирования.

Простой подход - использовать случайную последовательность Фишера-Йейтса для генерации сделок и фильтрации сделок, которые не соответствуют уже собранной информации. Knuth shuffle занимает ~ 1400 циклов ЦП на сделку, в основном потому, что мне нужно сгенерировать 51 случайное число и поменять 51 раз записи в таблице.

Это не имеет значения для обычных случаев, когда мне нужно будет генерировать только 10000-100000 сделок за 7 минут. Но есть крайние случаи, когда фильтры могут выбирать только очень маленькое подмножество рук, требующее генерации огромного количества сделок.

Использование одного номера для нескольких карт

При профилировании с помощью callgrind (valgrind) я заметил, что основным замедлением был генератор случайных чисел в C ++ (после переключения с std ::iform_int_distribution, который был первым узким местом).

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

int number = uniform_rng(0, 52*51*50*49);
int card1 = number % 52;
number /= 52;
int cards2 = number % 51;
number /= 51;
......

Конечно, это только незначительная оптимизация, потому что генерация все еще O (N).

Генерация с использованием битовых перестановок

Следующей идеей было именно то решение, которое было задано здесь, но я все равно получил O (N), но с большей стоимостью, чем первоначальный случайный случай. Но давайте посмотрим на решение и почему оно так неудачно.

Я решил использовать идею Сделка все сделки Джон Кристман

void Deal::generate()
{
    // 52:26 split, 52!/(26!)**2 = 495,918,532,948,1041
    max = 495918532948104LU;
    partner = uniform_rng(eng1, max);

    // 2x 26:13 splits, (26!)**2/(13!)**2 = 10,400,600**2
    max = 10400600LU*10400600LU;
    hands = uniform_rng(eng2, max);

    // Create 104 bit presentation of deal (2 bits per card)
    select_deal(id, partner, hands);
}

Пока что хорошо и довольно красиво, но реализация select_deal - это PITA.

void select_deal(Id &new_id, uint64_t partner, uint64_t hands)
{
    unsigned idx;
    unsigned e, n, ns = 26;

    e = n = 13;

    // Figure out partnership who owns which card
    for (idx = CARDS_IN_SUIT*NUM_SUITS; idx > 0; ) {
        uint64_t cut = ncr(idx - 1, ns);

        if (partner >= cut) {
            partner -= cut;
            // Figure out if N or S holds the card
            ns--;
            cut = ncr(ns, n) * 10400600LU;

            if (hands > cut) {
                hands -= cut;
                n--;
            } else
                new_id[idx%NUM_SUITS] |= 1 << (idx/NUM_SUITS);

        } else
            new_id[idx%NUM_SUITS + NUM_SUITS] |= 1 << (idx/NUM_SUITS);

        idx--;
    }

    unsigned ew = 26;
    // Figure out if E or W holds a card
    for (idx = CARDS_IN_SUIT*NUM_SUITS; idx-- > 0; ) {
        if (new_id[idx%NUM_SUITS + NUM_SUITS] & (1 << (idx/NUM_SUITS))) {
            uint64_t cut = ncr(--ew, e);

            if (hands >= cut) {
                hands -= cut;
                e--;
            } else
                new_id[idx%NUM_SUITS] |= 1 << (idx/NUM_SUITS);

        }
    }
}

Теперь, когда у меня было решение перестановки O (N), чтобы доказать, что алгоритм работает, я начал искать отображение O (1) от случайного числа к перестановке битов. Жаль, что похоже, что единственным решением было бы использование огромных таблиц подстановки, которые могли бы уничтожить кэш процессора Это не очень хорошая идея для ИИ, который будет использовать очень большое количество кешей для анализатора с двойным манекеном.

Математическое решение

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

У меня еще нет готового кода для этого, чтобы проверить, сколько циклов я трачу в общем случае, когда фильтр выбирает основную часть сделки. Но я считаю, что этот подход обеспечивает наиболее стабильную производительность генерации, сохраняя стоимость менее 0,1%.

1 голос
/ 20 июня 2010

Сгенерируйте N чисел (N <от числа нужных вам случайных чисел), прежде чем выполнять вычисления, или сохраните их в массиве в виде данных с помощью вашего медленного, но хорошего генератора случайных чисел; затем выберите число, просто увеличив индекс на массив внутри вашего цикла вычислений; если вам нужны разные семена, создайте несколько таблиц. </p>

1 голос
/ 20 июня 2010

Обычно нет необходимости в полном диапазоне следующего случайного значения, поэтому для использования точно такой же степени случайности вы можете использовать следующий подход (который, как я думаю, почти аналогичен случайному (0, N!)):*

// ...
m = 1; // range of random buffer (single variant)
r = 0; // random buffer (number zero)
// ... 
for(/* ... */) {
    while (m < n) { // range of our buffer is too narrow for "n"
        r = r*RAND_MAX + random(); // add another random to our random-buffer
        m *= RAND_MAX; // update range of random-buffer
    }
    x = r % n; // pull-out  next random with range "n"
    r /= n; // remove it from random-buffer
    m /= n; // fix range of random-buffer
    // ...
}

PS Конечно, будут некоторые ошибки, связанные с делением на значение, отличное от 2 ^ n, но они будут распределены по приведенным выборкам.

1 голос
/ 20 июня 2010

Как показывают другие ответы, вы можете сделать случайное целое число в диапазоне от 0 до N! и использовать его для создания шаффла. Хотя теоретически правильно, это не будет быстрее, так как N! быстро растет, и вы будете тратить все свое время на арифметику bigint.

Если вам нужна скорость, и вы не против обменять какую-то случайность, вам будет намного лучше, если использовать менее хороший генератор случайных чисел. Линейный конгруэнтный генератор (см. http://en.wikipedia.org/wiki/Linear_congruential_generator) даст вам случайное число за несколько циклов.

1 голос
/ 20 июня 2010

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

Вероятно, самый известный алгоритм
http://en.wikipedia.org/wiki/Linear_congruential_generator

Подробнее
http://en.wikipedia.org/wiki/List_of_pseudorandom_number_generators

0 голосов
/ 20 июня 2010

Генерирует 32 битное целое число. Для каждого индекса i (возможно, только до половины числа элементов в массиве), если бит i % 32 равен 1, поменяйте местами i с n - i - 1.

Конечно, это может быть недостаточно случайным для ваших целей. Возможно, вы могли бы улучшить это, не меняя местами n - i - 1, а используя другую функцию, примененную к n и i, которая обеспечивает лучшее распределение. Вы даже можете использовать две функции: одна для бита 0 и другая для 1.

...