我认为你的两遍算法可能是你能做的最好的,考虑到你在评论中添加的限制,你事先不知道哪些卡有资格进行给定的平局。
您可以尝试狡猾的“一次从未知大小的列表中随机选择”算法:
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) 为第 k 步后卡号 j 是当前选择的卡的概率。
需要证明:对于所有 n,p(j,n) = 1/n 对于所有 1 <= j <= n
对于 n = 1,显然 p(1,1) = 1,因为在第一步中选择第一张牌的概率为 1/1 = 1。
假设对于所有 1 <= j <= k,p(j,k) = 1/k。
然后我们在步骤 (k+1) 以概率 1/(k+1) 选择第 (k+1) 张卡片,即 p(k+1,k+1) = 1/(k+1)。
我们以概率 k/(k+1) 保留现有选择,因此对于任何 j < k+1:
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
因此,通过归纳,对于所有 n: p(j,n) = 1/n for all 1 <= j <= n]