从 n 个数字中选出 m 个不同的数字,保证这 m 个数字是等概率的
问题如上。
这是我被面试的一个题目。
我的第一反应给出的解决的方法是。开启 n 个线程并标记序号,各个线程打印出它的序号。直到有 m 个线程被调度时,停止全部线程。
打印出的序号即是 m 个等概率出现的数字。
面试官听到这个解决的方法,吸了一口凉气。预计心里在想,这小伙疯了!我当时自知这个解决方式不是面试官想要的。于是说了,假设这个 n 非常大,那么就要另
想办法了,由于不可能在一个进程里产生随意多个线程。
想啊想,过了两分钟。还是没有找到解决的方法。面试官非常 nice 让我回去后再想一想。
事实上这个题细想。有些难度。你可能有一种思路:假设能一次性取出 m 个数字。再保证各数字是随机的。则能够满足等概率。但。
。。
怎样一次性取出 m 个数字呢?
一次性生成多个随机数?假设生成的数字有同样则不是等概率的。由于这个数字比其他的数字出现的次数多,则概率大些(虽然你忽略了它出现多次)。
或者还有思路:
每次出一个,保证取 m 次得到的各数字概率相等。听起来。似乎这样的思路要难些。
[解法一]
我们来模拟这个过程。设有一个长度为 m 的辅助数组 B,用来装选中的数字。数组末满时,依次从长度为 n 的数组 A 中每次取一个数字顺序放入 B 中。直到 B 满了。
设对于兴许的每个元素,其装入 B 中的概率为 x。此元素装入 B 中的操作是将它与 B 中的某个元素置换。
则 B 中已经存在的某个元素,继续在 B 中存在的概率为:(1-x) + x*(m-1)/m。即当前取出的元素不进入 B。被直接舍弃,或者当前取出的元素进入 B ,但置换不发生在这个元素身上。
当前 A 中取出的元素。在 B 中存在的概率为 x。即,仅仅要这个元素被选中,就一定会进入 B。
因为要使用每一个元素的概率相等。则有:
x = (1-x) + x*(m-1)/m。故 x = m/(m+1)。
也即。按上面的操作便能保证每个被留下来的元素的概率相等且为 x ,即 m/(m+1)。
[解法二]
事实上。我们考虑一下,这个模型不就是抽奖的模型吗,有 n 张彩票,n 个人每人一张,怎样选出 m 个人出来中奖。即。我们仅仅须要模拟一个公正的抽奖过程便能得到等概率的 m 个人。
我们都知道,抽奖不分先后。每一个人中奖的机率都一样。因此。最简单的做法是将 n 个人随机化排成一列,再取前 m 个人中奖就可以。
那么,我们借助洗牌算法便能做到。那么,怎样得到一个好的洗牌算法呢?一个能够证明是均匀的方法例如以下:
对于第 i 张牌,它以 i/(i+1) 的概率与前面 i 张牌交换,实际操作时,能够生成一个 0 ~ i 之间的随机数。当其不为 i 时运行交换。交换的操作是:将此牌与前面 i 张牌随机交换。
于是,能够证明。第 i 张牌在位置 i ,也即。它没有发生交换的概率为 1 - i/(i + 1)= 1/(i+1)。
第 i 张牌在前面不论什么一个位置的概率为 i/(i+1)*1/i = 1/(i+1) 。可是,我们还须要证明,前 i 张牌中的随意一张在前面 i 个位置中的随意一个位置的概率为 1/(i+1) 才算是证明全然。假设直接入手。这个证明能够想象是相当复杂的。我们使用数学归纳法证明,按上面的操作,随意第 i 张牌被操作完毕后,总共的 i + 1 张牌中的随意一张在0 ~ i 的随意一个位置上的概率为 1/(i + 1)。
证明:
当仅仅有一张牌(第 0 张牌)时,在位置 0 上的概率为 1。
如果第 i 张牌被操作完毕后。总共的 i + 1 张牌中的随意一张在0 ~ i 的随意一个位置上的概率为 1/(i + 1)。
则对于第 i + 1 张牌被操作后:
前面已经证明过。第 i+1 张牌放置在 0 ~ i+1 中的任何位置的概率为 1/(i+2)。
对于 0 ~ i 中的随意一张牌 x,它原先在 0 ~ i 上任何位置的概率为 1/(i + 1)。
x 被换到第 i+1 位置的概率为 (i+1)/(i+2) * 1/(i+1) = 1/(i+2)。x 如今还在 0 ~ i 位置的概率即 1 减去前者,为 : 1 - 1/(i+2)。
而 0 ~ i 共同拥有 i + 1 个位置,故 x 在随意一个位置的概率为:
(1 - 1/(i+2))*1/(i+1) 结果为 1/(i+2)。
于是就证明原结论。因此。这是一个平衡的洗牌算法。
[解法三]
假设我们每次在面临第 i 个元素,不是像解法一中的,维持一个固定的概率去决定该元素是否留下,而是与当前已处理过的元素个数相关,是否能得到一个解法呢?
设总共已处理的个数为 N。当前正要处理的是第 i 个元素。辅助数组为 B。源数组为 A。操作例如以下:
1.当 i <= m 时。元素留下。
2.当 i > m 时,使用概率 m/N 决定元素的去留。假设元素留下。则它随机与 B 中某个元素 x 置换(丢弃x,保留该元素)
证明等概率:
1.当 i = m + 1 时,此元素留下的概率为 m/(m+1)。B 中随意一个元素留下的概率为:1-m/(m+1) + m/(m+1)*(m-1)/m = m/(m+1)。故此时,B 中全部元素的概率为 m/(m+1)。
2.设当已处理 N 个元素时,B 中的元素的概率为 m/N。
则当已处理 N+1 个元素时,当前元素留下的概率为 m/(N+1)。B 中随意一个元素留下的概率为:m/N*(1-m/(N+1) + m/(N+1)*(m-1)/m) = m/(N+1)。最前面的 m/N 表示此元素在 B 中,否则不在 B 中。
故使用上面的操作方法。留下的元素的概率是相等的。且与总共处理的元素的个数是相关的。
这一模型和解法一中的模型是不同样的。注意差别:
解法一中的模型适用于保留下来的元素概率相等。且永远不变。
解法二中的模型适用于保留下来的元素概率相等。但随着处理的元素的个数添加而改变,这也意味着,当须要从未知数目的数据源中取 m 个数字,使其等概率,这样的方法是很适用的。
[解法四]
我们已经有一个心得了,解法方案好像类似于:面临当前元素时。使用一个概率(这个概率可能是动态变化的。或者不变的)决定去留,若留,则与某个已选择的元素置换。以下再给出一种方法。
设 A 为源数组。B 为辅助数组(装入已选择的元素)。A 长度为 n。B 长度为 m。须要从 A 中取 m 个数字放入 B。使它们等概率。
遍历 A,在面临第 i 个元素 x 时,记 p 为还须要从 A 中选出的元素个数。q 为从 x 向后数,将 A 数完的个数。包含 x。决定 x 被选中的概率设置为 p/q。这也能够达到等概率。
1:第 0 个元素被选中的概率为 :m/n
2:第 1 个元素被选中的概率为 :m/n*(m-1)/(n-1) + (1-m/n)*m/(n-1) = m/n
3:第 2 个元素被选中的概率为:... = m/n
....
依此类推,不管哪个元素被选中的概率都为 m/n。以下,我们证明随意一个元素被选中的概率都为 m/n。
假设按上面的思路去证明将非常复杂。可是有一个非常巧妙的证明方法。
我们看这个问题的模型,实际上,它就是一个抽奖模型,如今有一个箱子里面装着 n 张奖券,写着“中”。或“不中”,当中。写着“中”的有 m 张,如今问。第 k 次抽奖,中奖的概率为
多少?这显然为 m/n!
还记得 "抽奖与顺序无关” 吗?于是。我们独立写出第 k 次中奖的概率的表达式:
C(m,1)*A(n-1,m-1) / A(n,m) = m/n。
故,上面的操作方法,随意一个元素被选中的概率都为 m/n。
解法四是对抽奖的全过程进行概率模拟,而解法二是对抽奖的前置处理进行模拟。
此解法的模型适用于保留下来的元素概率固定且相等。