Создать последовательность случайных чисел без повторов - PullRequest
37 голосов
/ 29 марта 2009

Дубликат:

Уникальные случайные числа в O (1)?

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

Например:

случайная (10)

может вернуться 5, 9, 1, 4, 2, 8, 3, 7, 6, 10

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


Edit:

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


Edit:

Я вижу, что все предлагают алгоритмы перемешивания. Но если я хочу сгенерировать большое случайное число (1024 байт +), тогда этот метод занял бы намного больше памяти, чем если бы я просто использовал обычный ГСЧ и вставлял его в набор до тех пор, пока он не достигнет указанной длины, верно? Нет лучшего математического алгоритма для этого.

Ответы [ 28 ]

28 голосов
/ 29 марта 2009

Вас может заинтересовать сдвиговый регистр с линейной обратной связью. Мы привыкли создавать их из аппаратного обеспечения, но я также делал это из программного обеспечения. Он использует сдвиговый регистр с некоторыми битами xor'ed и возвращается на вход, и если вы выберете только правильные «касания», вы можете получить последовательность, равную размеру регистра. То есть 16-битный lfsr может создать последовательность длиной 65535 без повторов. Это статистически случайный, но, конечно, чрезвычайно повторяемый. Кроме того, если это сделано неправильно, вы можете получить несколько удивительно короткие последовательности. Если вы посмотрите на lfsr, вы найдете примеры того, как их правильно построить (то есть, «максимальная длина»).

17 голосов
/ 29 марта 2009

Тасовка - отличный способ сделать это (при условии, что вы не вводите смещение с использованием наивного алгоритма). См. Фишер-Йейтс shuffle .

16 голосов
/ 29 марта 2009

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

Подробнее о перемешивании здесь: Создание случайного упорядоченного списка из упорядоченного списка

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

В этом случае поиск по списку пока будет намного более эффективным. Таким образом, идеальным вариантом будет использование эвристики (определяемой экспериментом), чтобы выбрать правильную реализацию для заданных аргументов. (Извиняюсь за приведение примеров на C #, а не на C ++, но ASFAC ++ B Я учу себя думать на C #).

IEnumerable<int> GenerateRandomNumbers(int range, int quantity)
{
    int[] a = new int[quantity];

    if (range < Threshold)
    {
        for (int n = 0; n < range; n++)
            a[n] = n;

        Shuffle(a);
    }
    else
    {
        HashSet<int> used = new HashSet<int>();

        for (int n = 0; n < quantity; n++)
        {
            int r = Random(range);

             while (!used.Add(r))
                 r = Random(range);

             a[n] = r;
        }
    }

    return a;
}

Расходы на проверку повторяющихся чисел, цикл при наличии коллизий и т. Д. Будут дорогими, но, скорее всего, будет некоторое значение Threshold, когда оно станет быстрее, чем выделение для всего диапазона.

Для требований относительно небольшого количества может быть быстрее использовать массив для used и выполнять линейный поиск в нем из-за большей локализации, меньших накладных расходов, дешевизны сравнения ...

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

IEnumerable<int> ForLargeQuantityAndRange(int quantity, int range)
{
    for (int n = 0; n < quantity; n++)
    {
        int r = Random(range);

        while (!used.Add(r))
            r = Random(range);

        yield return r;
    }
}
7 голосов
/ 29 марта 2009

Если случайное число гарантированно никогда не повторится, оно больше не является случайным, и количество случайность уменьшается по мере генерирования чисел (после девяти чисел random(10) довольно предсказуемо, и даже после восьми вы есть шанс 50-50).

4 голосов
/ 29 марта 2009

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

Вместо этого используйте обратимый псевдослучайный хеш. Затем введите значения 0 1 2 3 4 5 6 и т. Д. По очереди.

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

Вот тот, который будет работать, например, если вы хотите просмотреть все 2 ^ 32 32-битных значений. Это проще всего написать, потому что неявный мод 2 ^ 32 целочисленной математики в этом случае работает на вас.

unsigned int reversableHash(unsigned int x)
{
   x*=0xDEADBEEF;
   x=x^(x>>17);
   x*=0x01234567;
   x+=0x88776655;
   x=x^(x>>4);
   x=x^(x>>9);
   x*=0x91827363;
   x=x^(x>>7);
   x=x^(x>>11);
   x=x^(x>>20);
   x*=0x77773333;
   return x;
}
3 голосов
/ 29 марта 2009

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

3 голосов
/ 29 марта 2009

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

<ч /> Ответ на правки и комментарии:

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

2 голосов
/ 29 марта 2009

Как насчет использования генератора GUID (как в .NET). Конечно, не гарантируется, что дубликатов не будет, однако вероятность их получения довольно мала.

1 голос
/ 29 марта 2009

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

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

Конечно, если у вас плохой генератор случайных чисел (например, уязвимость Debian SSL для генератора случайных чисел ) или вы генерируете достаточно маленькие числа, чтобы парадокс дня рождения 1008 * дал вам высока вероятность столкновения, тогда вам нужно будет что-то сделать, чтобы избежать повторов. Но для больших случайных чисел с хорошим генератором просто доверяйте вероятности, чтобы не повторять вас.

1 голос
/ 29 марта 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...