从N个元素中均匀随机选择M个元素-混淆

Uniformly and randomly choose M elements from N elements - confused

我知道有人问过类似的问题

Choose m elements randomly from a vector containing n elements

Pick N items at random from sequence of unknown length

可是越看越糊涂

Uniformly and randomly choose M elements from N elements

所以我需要从N个元素中挑选出M个元素。而且我还需要使每个元素被选中的概率均匀分布:M/N

我的直觉是

  1. 随机选择一个元素
  2. 拿出来
  3. 对其余元素重复该过程

我猜这个解决方案是错误的? M 个选定元素的概率为 1/N1/(N-1)、...、1/(N-M) M/N , 我说的对吗?


一个可能的正确解决方案是

  1. 对整个N个元素做一个洗牌
  2. 选出前M个。

但是我计算不出每个元素被选中的概率。

谁能证明这个解的概率确实是M/N


当然我们也可以用Reservoir sampling,它的概率是M / N.

我认为您的困惑可能在于您没有区分选择序列和选择集合。

在您的第一个程序中,仅仅因为您只有 1/N 机会在第一轮中选择特定元素,并不意味着您不会在后续轮中选择它。该元素有 1/N 的机会成为结果中的第一个元素...但它有 M/N 的机会在 some 轮中被选中。这样就可以了。取M=2,N=4:一个元素被选中的几率是1/4 + (3/4)*(1/3) = 2/4.

至于你的第二个过程,在洗牌之后,每个元素在数组中的位置是均匀分布的,所以它的位置有 M/N 的机会等于或小于 M(因此被选中) .所以这也行。

元素被选中的概率:

First: 1/N;
Second: 1/(N-1) * (1 - 1/N) = 1/N
...

其中(1 - 1/N)是第二个物品在第一步没有被捡到的概率,也就是它在第二步被捡到的条件。这里 1/N 是在第一步中选择某些元素的概率,而 (1 - 1/N) - 在第二步中选择的元素可用于在第二步中选择的概率。

Third: 1/(N-2) * (1 - 1/N - 1/N) = 1/N

其中 (1 - 1/N - 1/N) 是元素在第三步可供选择的概率。

以此类推

这里的重点是,对于任何元素,在一个步骤中选择的概率是 1/N

尽管 shuffle 方法有效,但还有另一种方法不需要修改原始数组。事实上,您甚至不需要知道总共有多少项。您可以从流中随机选择 M 个项目,而您唯一需要保存的数据是这 M 个项目的数组。

基本思想是从长度未知的流中选取单个项目的算法的扩展。同样,您不知道流中有多少项。因此,您始终拥有 selected 项目,但您可以随时更改 selected 项目。

开始时没有 selected 项目,计数为 0。当第一个项目出现时,您增加计数。然后你在 0 到 count-1 范围内选择一个随机数。如果该值为 0,则您将当前项目设为 selected 项目。选择的第一个随机数必须是 0,因为您的范围是 0 到 0。

当您阅读每个项目时,您会增加计数,选择​​随机数,如果选择的随机数为 0,则使当前项目成为 selected 项目。它看起来像这样:

selected_item = none
count = 0
for each item
    count = count + 1
    if (random(count) == 0)
        selected_item = current_item

这是有效的,因为 select阅读当前项目的机会随着每个项目的阅读而减少。也就是说,第一项 select 的概率为 1/1。当第二个物品进来时,你有 1/2 的几率 select 它会取代第一个物品。当您收到第三件物品时,您有 1/3 的机会将当前 selected 物品替换为新物品。

当您到达流的末尾时,您将给予每个项目同等的机会 selected。

您可以很容易地将其扩展到多个项目。您首先 selecting 进来的前 M 个项目,然后将它们放在 selected 项目数组中。然后,每当有新项目出现时,您会在 0 和 count-1 之间选择一个随机数,如果该数字小于 M,则您会用新项目随机替换 selected 项目中的一个。它看起来像这样:

selected_items = array of M items
count = 0
for each item
    if (count < M)
        selected_items[count] = current_item
    else
        if (random(count) < M)
            // pick a random number from 0 to M-1
            // and replace that item with the new item
            ix = random(M)
            selected_items[ix] = current_item

我在我的博客 Random selection 中写了更多关于这个的细节。