随机采样问题

问题:从 1到n个数中随机选出m个不同的数。(编程珠玑12章)

第一种算法,从r个剩余的整数中选出s个,以概率s/r选择下一个数

initialize set S to empty
  Size:=0
  while Size<m do
      T:=RandInt(1,N)
      if T is not in S then
          insert T in S
          Size:=Size+1

这个算法有一个致命的缺陷是每新产生一个随机数都要判断其是否已经被集合S包含。通常情况下,如果不借用其它数据结构,就需要遍历整个集合S。最极端的情况是m=n且T:=RandInt(1,N) 。

 

一种改进算法是

for I:=1 to N do
      X[I]=I;
  for I:=1 to M do
  {
      J:=RandInt(I,N);
      Swap(X[J],X[I]);
  }

这个算法的优点:

1.不需要判断产生的随机数是否已经存在。

2.产生的随机数是无序的,适合排序。

3.其算法性能不受M与N的关系限制。

其代价就是要多消耗O(N-M)的空间,其运行时间为O(N)。如果N>>M,这个算法无论从哪个角度讲都是无法接受的。

 

下面我们给出Floyd算法,在N>>M时,更有效率。

function Sample(M,N)
      if M=0 then 
          return the empty set
      else
          S:=Sample(M-1,N-1)
          T:=RandInt(1,N)
          if T is not in S then
              insert T in S
          else
              insert N in S

Floyd算法的结构很容易递归的理解:为了从1..10中产生一个5元素样本,首先从1..9中产生一个4元素样本,然后在加上第5个元素。

用归纳法证明每个元素被取到的概率是一样的。

当M=1时,显然成立。假设Sample(M-1,N-1)成立,即1..N-1被取到的概率都是M-1/N-1。

那么在Sample(M,N)中,N被取到的概率为1/N+M-1/N=M/N.(T=RandInt(1,N),T为N的概率是1/N,T不为N但是已经被选过,即T是N个数里面选M-1个数的概率,等于M-1/N

对于1..N-1中的任意数一个数,在Sample(M,N)中被取到的概率是M-1/N-1+(1-M-1/N-1)/N=M/N.(在Sample(M-1,N-1)被取到M-1/N-1,没被取到记为A=1-(M-1/N-1),在T=RandInt(1,N)里面取到的概率为1/N,所以总概率为M-1/N-1 + A*1/N=M/N)证毕

Floyd通过引入一个新变量将上述算法改写成迭代形式:

initialize set S to empty
  for J:=N-M+1 to N do
      T:=RandInt(1,J)
      if T is not in S then
          insert T in S
      else
          insert J in S

 改进后的算法虽然取消了递归,但是这个M个随机数是从小到大排列的。某些情况可能要求随机数是随机分布的。事实上可以对算法稍微修改下,比如引入随机位置插入新产生的随机数就可以满足要求。

 

下面是用c++实现的floyd算法

void Floyd(int m, int n)
 {
     set<int> outSet;
 
     for(int j=n-m;j<n;j++)//下标0-(n-1)
     {
         int t = rand()%j;
 
         if (outSet.find(t)!=outSet.end())
         {
             outSet.insert(j-1);
         }
         else
             outSet.insert(t);
     }
 
     set<int>::iterator it;
     for(it=outSet.begin();it!=outSet.end();it++)
         cout<<" " << *it;
     cout << endl;
 }

对于这个问题,还有一种考虑的解法:

首先扫描一遍链表,对每个节点赋予一个随机的值(譬如一个随机整数);然后使用一种Top K算法(譬如最大K个整数)得到需要的K个节点。

 

本文参考了2010Freeze的文章,链接http://www.cnblogs.com/2010Freeze/archive/2012/02/27/2370284.html


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值