Ответ 1
Там очень хороший и эффективный алгоритм для этого, используя метод выборки коллектора.
Позвольте мне начать с истории:
Knuth вызывает этот алгоритм R на p. 144 его выпускных изданий 1997 года "Семинумерные алгоритмы" (том 2 "Искусство компьютерного программирования" ) и предоставляет для него некоторый код. Кнут приписывает алгоритм Алану Г. Уотерману. Несмотря на длительный поиск, я не смог найти оригинальный документ Waterman, если он существует, и, возможно, вы чаще всего увидите, как Кнут цитирует его как источник этого алгоритма.
McLeod and Bellhouse, 1983 (1) дают более подробное обсуждение, чем Knuth, а также первое опубликованное доказательство (что я знаю), что алгоритм работает.
Vitter 1985 (2) рассматривает алгоритм R, а затем представляет собой еще три алгоритма, которые обеспечивают один и тот же вывод, но с завихрением. Вместо того, чтобы делать выбор для включения или пропуска каждого входящего элемента, его алгоритм предопределяет количество пропущенных входящих элементов. В своих тестах (которые, по общему признанию, устарели сейчас) это значительно сократило время выполнения, избегая генерации случайных чисел и сравнений по каждому входящему числу.
В псевдокоде алгоритм:
Let R be the result array of size s
Let I be an input queue
> Fill the reservoir array
for j in the range [1,s]:
R[j]=I.pop()
elements_seen=s
while I is not empty:
elements_seen+=1
j=random(1,elements_seen) > This is inclusive
if j<=s:
R[j]=I.pop()
else:
I.pop()
Обратите внимание, что я специально написал код, чтобы не указывать размер ввода. Это один из интересных свойств этого алгоритма: вы можете запустить его, не задумываясь заранее о размере ввода, и он все же гарантирует, что каждый элемент, с которым вы сталкиваетесь, имеет равную вероятность попасть в R
(то есть там не является предубеждением). Кроме того, R
содержит справедливую и репрезентативную выборку элементов, которые алгоритм рассматривал в любое время. Это означает, что вы можете использовать это как онлайн-алгоритм .
Почему это работает?
McLeod and Bellhouse (1983) предоставляют доказательство с использованием математики комбинаций. Это довольно, но было бы немного сложно восстановить его здесь. Поэтому я создал альтернативное доказательство, которое легче объяснить.
Проведем доказательство по индукции.
Скажем, мы хотим сгенерировать набор элементов s
и что мы уже видели элементы n>s
.
Предположим, что наши текущие элементы s
уже были выбраны с вероятностью s/n
.
По определению алгоритма мы выбираем элемент n+1
с вероятностью s/(n+1)
.
Каждый элемент, уже входящий в наш результирующий набор, имеет вероятность замены 1/s
.
Вероятность того, что элемент из набора результатов n
-seen заменяется в наборе результатов n+1
-seen, поэтому (1/s)*s/(n+1)=1/(n+1)
. Наоборот, вероятность того, что элемент не будет заменен, равна 1-1/(n+1)=n/(n+1)
.
Таким образом, набор результатов n+1
-seen содержит элемент, либо если он был частью набора результатов n
-seed и не был заменен --- эта вероятность равна (s/n)*n/(n+1)=s/(n+1)
--- или если элемент был выбран --- с вероятностью s/(n+1)
.
Определение алгоритма говорит нам, что первые s
элементы автоматически включаются в число первых n=s
членов результирующего набора. Поэтому набор результатов n-seen
включает в себя каждый элемент с вероятностью s/n
(= 1), который дает нам необходимый базовый случай для индукции.
Ссылки
-
McLeod, A. Ian и David R. Bellhouse. "Удобный алгоритм для рисования простой случайной выборки". Журнал Королевского статистического общества. Серия C (прикладная статистика) 32.2 (1983): 182-184. (Ссылка)
-
Виттер, Джеффри С. "Случайная выборка с резервуаром". ACM-транзакции по математическому программному обеспечению (TOMS) 11.1 (1985): 37-57. (Ссылка)