首页 > 代码库 > 【算法导论学习-012】n个数随机等概率的抽样m个

【算法导论学习-012】n个数随机等概率的抽样m个

算法法导论》P129页课后题5.3-7

     suppose we want to create a random sample of the set {1,2,3,…,n}, thatis, an m-element subset S, where0≤m≤n, such that each m-subset is equally likely to be created. One waywould be to set A[i]=i for i=1,2,3,…,n, call RANDOMIZE-IN-PLACE(A), and then take just the first marray elements. This method would make n calls to the RANDOM procedure.If n is much larger than m, we can create a random samplewith fewer calls to RANDOM. Show that the following recursive procedurereturns a random m-subset S of {1,2,…,n}, in which eachm-subset is equally likely, while making only m calls to RANDOM:

    

RANDOM-SAMPLE(m,n)
if m == 0
    return ?
else
    S = RANDOM-SAMPLE(m-1, n-1)
    i = RANDOM(1,n)
    if i ∈ S
        S = S ∪ {n}
    else
        S = S ∪ {i}
    return S

     翻译过来就是:n个数随机等概率的取样m个。

    该题的证明方法1:http://clrs.skanev.com/05/03/07.html

    该题的证明方法2 :http://www.cnblogs.com/Jiajun/archive/2013/05/15/3080111.html

    题目中其实给出了两种解决方案。

方案1:调用RANDOMIZE-IN-PLACE(A)

/**  
 * 创建时间:2014年8月13日 上午9:46:51  
 * 项目名称:Test  
 * @author Cao Yanfeng  
 * @since JDK 1.6.0_21  
 * 类说明:  
 */
public class RandomSampleTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] array={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
		int[] result=randomSample(array, 5);
		for (int i : result) {
			System.out.println(i);
		}

	}
	public static int[] randomSample(int[] array,int m) {
		randomInPlace(array);
		int[] result=new int[m];
		for (int i = 0; i <m; i++) {
			result[i]=array[i];
		}
		return result;
		
	}
/*《算法导论》P126页伪代码*/
	public static void randomInPlace(int[] array) {
		int n=array.length;
		for (int i = 0; i < n; i++) {
			int index=random(i, n-1);
			if (array[i]!=array[index]) {
				array[i]^=array[index];
				array[index]^=array[i];
				array[i]^=array[index];
			}
		}
		
	}
	public static int random(int a,int b) {
		return new Random().nextInt(b-a+1)+a;
	}
}

方案2:实现题目中的伪代码

/** 

 * 创建时间:2014813上午9:46:51 

 * 项目名称:Test 

 * @authorCao Yanfeng 

 * @since JDK 1.6.0_21 

 * 类说明: 

 */

public classRandomSampleTest {

 

    /**

     * @param args

     */

    public static void main(String[] args) {

        // TODO Auto-generated method stub

        int[]array={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};

        LinkedList<Integer>result=randomSample(array, 5);

        for (Integerinteger : result) {

            System.out.println(integer);

        }

    }

   

    public staticLinkedList<Integer> randomSample(int[]array,intm){

        returnsample(array, array.length, m);

       

    }

    public staticLinkedList<Integer> sample(int[]array,intn,intm) {

        if (m==0) {

            return newLinkedList<Integer>();

        }else {

             LinkedList<Integer> s=sample(array,n-1, m-1);

            inti=array[random( 0,n-1)];

            if (s.contains(i)) {

                s.add(array[n-1]);

            }else {

                s.add(i);

            }

            returns;  

        }

    }  

/*返回闭区间的[a,b]随机数*/

public staticintrandom(inta,intb) {

        return new Random().nextInt(b-a+1)+a;

    }

}

方案3:赋予权重法

  《算法导论》P1225.3节的Randomized algorithms中提供的第一种随机采样方法即赋予权重法。但是权重有可能出现相同情况,不推荐这种方法。

方案4:蓄水池抽样

    见最后的扩展问题。

*******************************************************************************

    正如题目中所言,如果n个数据选取m个样本,如果n远大于m,则应该使用方案2,仅调用m次random()函数;如果n与m差距不大,则应该使用方案1,调用n次random()函数,但是方法简单。

*******************************************************************************

    扩展问题:【google面试题】给定一个数据流,其中包含无穷尽的搜索关键字(比如,人们在谷歌搜索时不断输入的关键字) 。如何才能从这个无穷尽的流中随机的选取 1000 个关键字?

    参考:http://blog.csdn.net/minglingji/article/details/7984445

    这也是“n个数随机等概率的取样m个”问题,但是n是未知的。采用的方式是蓄水池抽样。即:将数据流中的前1000个放入长度为1000的数组,对于1001个数,调用random(0,1000),[0,999]闭区间内每个数被选中的概率都是1000/1001。之后对于n>1000的每个数,[0,999]闭区间内每个数被选中的概率都是1000/n。这里random被调用的次数为n-m。下面模拟一下这个过程。

/**  
 * 创建时间:2014年8月13日 上午9:46:51  
 * 项目名称:Test  
 * @author Cao Yanfeng  
 * @since JDK 1.6.0_21  
 * 类说明:  
 */
public class RandomSampleTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] array={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
		int[] result=reservoirSample(array, 5);
		for (int i : result) {
		System.out.println(i);
	}
	}
	/*蓄水池抽样*/
	public static int[] reservoirSample(int[] array,int m) {
		int[] reservoir=new int[m];
		for (int i = 0; i <array.length; i++) {
			if (i<m) {
				reservoir[i]=array[i];	
			}else {
				int temp=random(0, i);
				if (temp<m) {
					reservoir[temp]=array[i];
				}
			}
			
		}
		return reservoir;
	}
}