刚好看到了记录一下:
“给出一个数据流,这个数据流的长度很大或者未知。并且对该数据流中数据只能访问一次。请写出一个随机选择算法,使得数据流中所有数据被选中的概率相等。”
首先,我们考虑从n个对象中等概率的选取1个对象。如果我们知道n的值,那么只需要从0-n-1中随机一个数选取它就可以了,被选中的概率是1/n。
如果我们不知道n的值的时候,这个问题就转换成了一个蓄水池抽样问题,即我们总是选择第一个对象,以1/2的概率选择第二个,以1/3的概率选择第三个,
以此类推,以1/m的概率选取地m个对象,当该过程结束时,对每个对象具有相同选中的概率,均为1/n。
证明如下:
第m个对象最终被选中的概率P = 选择m的概率*后面所有的对象均不被选中的概率
伪代码如下: i = 0 while more input items with probability 1.0 / ++i choice = this input item print choice
Java代码如下:
将k个中的代码k=1即可。
对于选取K的对象,思路类似。首先把读到的前K个对象放入“水库”,从第k+1个对象开始对于第m个对象,以k/m的概率选取,并以1/k的概率替换水库中的一个对象(貌似替换水库中的一个对象就是1/k,因为只能替换一个嘛),当这个过程结束,每个被选中的概率均为k/n。其实选取一个就是k=1这个特例
证明如下:
第m个对象被选中的概率=选择m的概率*(其后元素不被选中的概率+其后元素被选中*不替换已经在水库中的m的概率)
伪代码如下: array S[n]; //source, 0-based array R[k]; // result, 0-based integer i, j; // fill the reservoir array for each i in 0 to k - 1 do R[i] = S[i] done; // replace elements with gradually decreasing probability for each i in k to n do j = random(0, i); // important: inclusive range if j < k then R[j] = S[i] fi done java代码:
public static int[] doReservoirSampling(int[] source, int k){ int[] result = new int[k]; int index = -1; for (int i : source) { ++index; if (index < k) { //将前k项先保存进结果数组中 result[index] = source[index]; continue; } int j = random.nextInt(index + 1);//生成随机数 下标是从0开始,但是概率是从1开始计算的 if (j < k) { //一共可以产生0-index 这么多数,其中0到k-1都可以满足条件,这样概率就是k/index+1 result[j] = source[index]; } } return result; } 测试结果(10000次):
K=1的时候:
total used : 0ms 1 : 993 2 : 1000 3 : 968 4 : 988 5 : 990 6 : 990 7 : 1072 8 : 1039 9 : 950 10 : 1010
K=5的时候:
total used : 15ms 1 : 4959 2 : 5007 3 : 4968 4 : 5077 5 : 4964 6 : 4926 7 : 5037 8 : 4974 9 : 5088 10 : 5000
参考链接:
http://blog.jobbole.com/42550/
http://blog.csdn.net/huagong_adu/article/details/7619665