蓄水池抽样算法 从n个数中随机取m个数(JAVA实现)

实际问题

从n个数据中随机取得m个数,n>m    

算法大概思路

源数据的数组为a[]  大小为n    “水池”即存放取出的数据的数组为b[] 大小为m     若要做到随机 则每个数据被选中的概率则为m/n

进行如下操作

①对数组a[]进行遍历,设置j为遍历时当前元素下标,将a[]数组的前m个数据依次存入数组b[]中,此操作a[]数组中的前m个数被加入b[]数组的概率为100%即为1

②当j=m-1时,此时b[]数组的元素已满,若后面的元素要加入b[]数组,则要与池中的元素替换

替换的规则:(j为当前元素的下标,此时j>=m),在0-j的范围内随机出一个数字 r  ,r<m 则将j对应的元素(a[j])值赋给水池中的b[r],水池中r对应的数据被替换,被替换的概率为1/(j+1)---{因为数组从0开始,所以0-j有j+1个数}。而当前元素a[j]也就是j对应元素被选入水池的概率为r/(j+1)。

如果r>m,则j++向前移动,也就表示池中的数据没有被替换。

③j每向前移动一步则重复步骤②,直到数组a[]被完全遍历

分析前m个数和m之后的数被选入池中的概率

根据替换规矩可得出

①前m个数据   :

当j=m-1时,前m个数据被选入池中,概率为1:

当j=m时,水池中的数据被替换的概率为1/(j+1),即1/(m+1);则此轮留下概率为1-1/(m+1) =m/(m+1)   

当j=m+1时,水池中的数据被替换的概率为1/(j+1),即1/(m+2);则此轮留下的概率为1-1/(m+2)=(m+1)/(m+2)

当j=m+2时,水池中的数据被替换的概率为1/(j+1),即1/(m+3);则此轮留下的概率为1-1/(m+3)=(m+2)/(m+3)

.........以此类推

计算最终留下概率:

  1  *   m/(m+1)   *   (m+1)/(m+2)  *  (m+2)/(m+3)  *...*   (n-2)/(n-1)  *  (n-1)/(n) = m/n

②m以后的数据:

j<m时,不会被选中

当j=m时,m之后的数据被留下的概率为m/(j+1),即m/(m+1) 

当j=m+1时,m之后的数据被留下的概率为m/(j+1),即(m+1) /(m+2);

当j=m+2时,m之后的数据被留下的概率为m/(j+1),即(m+2)/(m+3)

.........以此类推

计算最终留下概率:

                m/(m+1)   *   (m+1)/(m+2)  *  (m+2)/(m+3)  *...*   (n-2)/(n-1)  *  (n-1)/(n) = m/n

结论

  前m个数和m之后的数被选入池中的概率是一样的

代码实现

//k[]表示源数组  m表示水池大小
public static int[] xushuichi(int k[], int m) {
         //b为水池
        int b[] = new int[m];
        if (k.length <= m)
            return new int[0];
        else if (k.length > m) {
            for (int j = 0; j < k.length; j++) {
                if (j < m)
                    b[j] = k[j];     // 将前m个数据存入数组
                else if (j >= m) {
                    //从0-j中随机出一个数
                    int r = new Random().nextInt(j + 1);
                    if (r < m)
                        b[r] = k[j];    //如果随机出的r<水池大小 ,则进行替换
                }
            }
        }
        return b;
    }

测试

 public static void main(String[] args) {
        int n = 5;
        int m = 4;
        int one = 0;
        int two = 0;
        int three = 0;
        int four = 0;
        int five = 0;
        int N[] = new int[n];
        for (int i = 0; i < n; i++)
            System.out.print((N[i] = i) + "  ");
        System.out.println();
        for (int j = 0; j < 10000000; j++) {
            int l[] = xushuichi(N, m);
            for (int q = 0; q < m; q++) {
                if (l[q] == 0)
                    one++;
                if (l[q] == 1)
                    two++;
                if (l[q] == 2)
                    three++;
                if (l[q] == 3)
                    four++;
                if (l[q] == 4)
                    five++;

            }
        }
        System.out.println("0 " + one + "  1 " + two + "   2 " + three + "   3 " + four + "   4 " + five);

源数组存放1,2,3,4,5 测试每个数据出现概率

result:

0  1  2  3  4  
0 7999128  1 8001228   2 8000456   3 7999657   4 7999531

概率基本一致。

猜你喜欢

转载自blog.csdn.net/qq_41800087/article/details/89422883