Я думаю, что ваш двухпроходный алгоритм, вероятно, будет лучшим, что вы можете сделать, учитывая ограничение, которое вы добавили в комментарии, что вы не знаете заранее, какие карты подходят для данного розыгрыша.
Вы можете попробовать хитрый алгоритм «случайного выбора из списка неизвестного размера за один проход»:
int sofar = 0;
int selected = -1;
for (i = 0; i < 52; ++i) {
if (used[i]) continue;
++sofar;
if ((rand() % sofar) == 0) selected = i;
}
if (selected == -1) panic; // there were no usable cards
else used[selected] = 1; // we have selected a card
Тогда, если (как вы говорите в комментарии) разные розыгрыши имеют разные критерии, вы можете заменить used[i]
на фактические критерии.
Способ работает так, что вы выбираете первую карту. Затем вы заменяете его второй картой с вероятностью 1/2. Замените результат третьей картой с вероятностью 1/3 и т. Д. По индукции легко доказать, что после n шагов вероятность того, что каждая из предыдущих карт будет выбранной, равна 1 / n.
В этом методе используется много случайных чисел, поэтому он, вероятно, медленнее, чем ваша двухпроходная версия, если не получить каждый элемент медленно, или оценка критериев медленная. Обычно он используется, например, для выбора случайной строки из файла, где вы действительно не хотите дважды просматривать данные. Он также чувствителен к смещению в случайных числах.
Хотя это хорошо и просто.
[Редактировать: доказательство
Пусть p (j, k) будет вероятностью того, что номер карты j будет выбранной в данный момент картой после шага k.
Требуется доказать: для всех n, p (j, n) = 1 / n для всех 1 <= j <= n </p>
Для n = 1, очевидно, p (1,1) = 1, поскольку первая карта выбирается на первом шаге с вероятностью 1/1 = 1.
Предположим, что p (j, k) = 1 / k для всех 1 <= j <= k. </p>
Затем мы выбираем (k + 1) -ю карту на шаге (k + 1) с вероятностью 1 / (k + 1), т.е. p (k + 1, k + 1) = 1 / (k + 1) .
Мы сохраняем существующий выбор с вероятностью k / (k + 1), поэтому для любого j
p(j,k+1) = p(j,k) * k/(k+1)
= 1/k * k/(k+1) // by the inductive hypothesis
= 1/(k+1)
Итак, p (j, k + 1) = 1 / (k + 1) для всех 1 <= k <= k + 1 </p>
Следовательно, по индукции для всех n: p (j, n) = 1 / n для всех 1 <= j <= n] </p>