Как убедиться, что случайно сгенерированные номера не повторяются? [дубликат]

Возможные Дубликаты:
уникальные случайные числа в O (1)?
как вы эффективно генерируете список из K неповторяющихся целых чисел между 0 и верхней границей n

Я хочу генерировать случайное число в определенном диапазоне, и я должен быть уверен, что каждое новое число не является дубликатом формирователей. Одним из решений является хранение ранее сгенерированных номеров в контейнере, и каждый новый номер проверяет aginst контейнер. Если такое число есть в контейнере, то генерируем agin, иначе используем и добавляем его в контейнер. Но с каждым новым номером эта операция становится все медленнее и медленнее. Есть ли лучший подход или любая функция rand, которая может работать быстрее и обеспечивать уникальность поколения?

EDIT: Да, существует ограничение (например, от 0 до 1.000.000.000). Но я хочу генерировать 100.000 уникальных чисел! (Было бы здорово, если бы решение было с помощью Qt особенности.)

20 ответов


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


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

  1. создайте случайное число, посмотрите, есть ли оно в (отсортированном) списке. Если не добавить и вернуть, попробуйте другой.
    • ваш список будет расти и потреблять память с каждым номером вы нуждаетесь. Если каждое число 32 бит, он будет расти по крайней мере 32 бит каждый раз.
    • каждое новое случайное число увеличивает коэффициент попадания, и это будет медленнее.
    • O (n^2) - я думаю
  2. создать битовый массив для каждого числа в диапазоне. Отметьте 1 / True, если уже возвращено.
    • каждое число теперь занимает только 1 бит, это все еще может быть проблемой, если диапазон большой, но каждое число теперь выделяет только 1 бит.
    • каждое новое случайное число увеличивает коэффициент попадания, и это сделает его медленнее.
    • O (n*2)
  3. заполнить перечислите все числа, перетасуйте их и верните N-е число.
    • список не будет расти, возвращаемые номера не будут замедляться,
    • но создание списка может занять много времени и много памяти.
    • O (1)

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


заполните список номерами, которые вам нужны, затем перетасуйте список и выберите свои номера с одного конца.


Если вы используете простой 32-битный линейный конгруэнтный RNG (например, так называемый "Минимальный Стандарт"), все, что вам нужно сделать, это сохранить начальное значение, которое вы используете, и сравнить каждое сгенерированное число с ним. Если вы когда-нибудь достигнете этого значения снова, ваша последовательность начинает повторяться, и у вас нет значений. Это O (1), но, конечно, ограничено значениями 2^32-1 (хотя, я полагаю, вы также можете использовать 64-разрядную версию).


существует класс генераторов псевдослучайных чисел, который, я считаю, имеет свойства, которые вы хотите:линейный конгруэнтный генератор. Если он определен правильно, он создаст список целых чисел от 0 до N-1, без повторения двух чисел, пока вы не используете все числа в списке один раз.

#include <stdint.h>

/*
 * Choose these values as follows:
 *
 * The MODULUS and INCREMENT must be relatively prime.
 * The MULTIPLIER-1 must be divisible by all prime factors of the MODULUS.
 * The MULTIPLIER-1 must be divisible by 4, if the MODULUS is divisible by 4.
 *
 * In addition, modulus must be <= 2**32 (0x0000000100000000ULL).
 *
 * A small example would be 8, 5, 3.
 * A larger example would be 256, 129, 251.
 * A useful example would be 0x0000000100000000ULL, 1664525, 1013904223.
 */

#define MODULUS    (0x0000000100000000ULL)
#define MULTIPLIER (1664525)
#define INCREMENT  (1013904223)

static uint64_t seed;

uint32_t lcg( void ) {
    uint64_t temp;

    temp = seed * MULTIPLIER + INCREMENT;   // 64-bit intermediate product
    seed = temp % MODULUS;                  // 32-bit end-result

    return (uint32_t) seed;
}

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


Это не было бы случайным, если есть такая картина?

насколько я знаю, вам придется хранить и фильтровать все нежелательные номера...


unsigned int N = 1000;
vector <unsigned int> vals(N);
for(unsigned int i = 0; i < vals.size(); ++i)
   vals[i] = i;
std::random_shuffle(vals.begin(), vals.end());

unsigned int random_number_1 = vals[0];
unsigned int random_number_2 = vals[1];
unsigned int random_number_3 = vals[2];
//etc

вы можете хранить числа в векторе и получать их по индексу (1..n-1). После каждой случайной генерации удалите индексированное число из вектора, а затем сгенерируйте следующее число в интервале 1..н-2. так далее.


Если они не могут быть повторены, они не случайны.

EDIT:

далее..

если они не могут быть повторены, они не вписываются в ограниченный компьютер


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


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


Если диапазон случайных чисел не имеет значения, вы можете использовать действительно большой диапазон случайных чисел и надеяться, что вы не получите никаких столкновений. Если ваш диапазон в миллиарды раз больше, чем количество элементов, которые вы ожидаете создать, ваши шансы на столкновение невелики, но все же есть. Если числа не должны иметь фактического случайного распределения, вы можете иметь номер из двух частей {counter}{random x digits} , который обеспечит уникальное число, но это не будет случайным распределенный.


не будет чисто функциональный подход, который не является O (n^2) на количество результатов, возвращенных до сих пор - каждый раз, когда число генерируется, вы будете нужно чтобы проверить каждый результат до сих пор. Кроме того, подумайте о том, что происходит, когда вы возвращаете, например, 1000 - е число из 1000-вам потребуется в среднем 1000 попыток, пока случайный алгоритм не придумает последнее неиспользуемое число, причем каждая попытка требует в среднем 499.5 сравнение с уже сгенерированными числами.

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


вы можете выделить достаточно памяти для массива битов с 1 битом для каждого возможного числа. и проверьте / установите биты для каждого сгенерированного числа. например, для чисел от 0 до 65535 потребуется только 8192 (8 Кбайт) памяти.


вот интересное решение, которое я придумал:

Предположим, у вас есть номера от 1 до 1000 - и у вас недостаточно памяти.

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

вы можете разделить массив на два, поэтому у вас есть массив 1-500 и один пустой массив

затем вы можете проверить, существует ли число в массиве 1 или не существует во втором матрица.

Итак, предполагая, что у вас есть 1000 чисел, вы можете получить случайное число от 1-1000. Если его меньше 500, проверьте массив 1 и удалите его, если он присутствует. Если его нет в массиве 2, Вы можете добавить его.

Это вдвое сокращает использование памяти.

Если вы предлагаете это с помощью рекурсии, вы можете разделить свой массив 500 на 250 и пустой массив.

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

поиск будет массово быстрее, потому что если вы сломаете его много, вы создадите число, такое как 29. Это меньше 500, меньше 250, меньше 125, меньше 62, меньше 31, больше 15, поэтому вы делаете эти 6 вычислений, а затем проверяете массив, содержащий в среднем 16/2 элементов - 8 в общей сложности.

Я должен запатентовать этот поиск, хотя я уверен, что он уже существует!


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

Почему?

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

получается, что статья в Википедии имеет некоторые примеры кода на C++, которые более проверены, чем все, что я бы дал вам с головы. Обратите внимание, что вы захотите вытягивать значения изнутри петель -- Петли просто повторяют регистр сдвига. Вы можете увидеть это в фрагмент здесь.

(Да, я знаю, что это было упомянуто, кратко в dupe-видел, как я пересматривал. Учитывая, что он не был поднят здесь и является лучшим способом решить вопрос плаката, Я думаю, что он должен быть поднят снова.)


предположим, size=100.000 затем создайте массив с этим размером. Создайте случайные числа, а затем поместите их в массив.Проблема, какой индекс будет ? randomNumber%size даст вам индекс.

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

например для последних секций 1231232444556 3458923444556

У вас никогда не будет таких номеров в вашем списке, даже если они совершенно разные, но последние разделы одинаковы.


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

подход сохранения всех сгенерированных чисел замедлит вычисление довольно быстро; чем больше чисел у вас есть, тем больше ваших потребностей в хранилище, пока вы не заполнили всю доступную память. Лучшим методом было бы (как кто-то уже предложил) использование известного генератора псевдослучайных чисел, такого как Линейный Конгруэнтный Генератор; это супер быстрый, требующий только модульного умножения и сложения, и теория за ним получает много упоминания в Vol. 2 из TAOCP кнута. Таким образом, теория гарантирует довольно большой период до повторения, и единственное, что требуется для хранения, - это используемые параметры и семена.


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


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

например :

HashSet<int> array = new HashSet<int>();
            array.Add(1);
            array.Add(2);
            array.Add(1);
            foreach (var item in array)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();