编程珠玑12章习题 12.9 FLoyd随机取样算法的证明(转自编程珠玑续)

13.3  随机排列

一些使用随机样本的程序要求样本的元素以随机的顺序出现。这样的序列被称为无重复的随机排列。例如,在测试一个排序程序的时候,随机产生的输入必须以随机的顺序出现;如果输入总是有序的,那么可能不能充分地测试排序代码。

我们可以利用Floyd算法F2产生一组随机样本,然后把它复制到一个数组中,最后打乱数组中元素的顺序。这段代码用于随机地打乱数组 的顺序:

 
 
  1. for I :M downto 2 do  
  2.     J :RandInt(1,  I)  
  3.     Swap(X[J], X[I]) 

这个只有三个步骤的方法调用了RandInt函数2M次。

当本章原来在《ACM通讯》上发表后,几位读者发现上面的伪代码经过小的修改后,能够从1..N的整数中产生M元随机排列并放入X[1..M]中:

 
 
  1. for I :1 to N do  
  2.     X[I] :I 
  3. for I :1 to M do  
  4.     J :RandInt(I, N)  
  5.     Swap(X[J], X[I]) 

这个算法很容易实现成代码,但是它需要O(N)的运行时间和O(N)的空间。下面我们会看到,Floyd的算法在N相对于M比较大的时候,相比之下会更有效率。

Floyd的随机排列产生器与他的算法F2类似。为了产生1~N内的一组M元排列,它会先从1~N 1中产生一组M 1元的排列。(算法的递归版本中没有变量J。)但是,排列产生器的主要数据结构是序列而非集合。下面是Floyd的算法P。

算法P

 
 
  1. initialize sequence S to empty  
  2. for J :N - M + 1 to N do  
  3.      T   = RandInt(1, J)  
  4.      if T is not in S then  
  5.           prefix T to S  
  6.       else  
  7.     insert J in S after T 

从习题5可以看出,算法P在随机位的使用上尤其高效。习题6讨论了序列S的高效率实现。

我们可以从算法在M=N时的行为得到关于算法P的直观的感觉,此时算法生成N元的随机排列,其中J从1到N循环。在执行循环体之前,S是一个1~J 1的整数的随机排列。循环体把J插入到序列中仍然保持了这一点;当T=J时,J成为第一个元素,否则J被随机放置于已经存在的J 1个元素的某一个之后。

一般地,算法P以等概率生成1~N内的每一个M元排列。Floyd对于正确性的证明用到循环不变式:第i轮循环后,J=N M+i且S可能是1~J中i个不同整数的任意排列,并且只有一种途径可以生成这个排列。

Doug McIlroy发现了一种优雅的方式来说明Floyd的证明:对于任何一个排列,有且仅有一种途径来生成它,因为算法是可以逆推的。例如,假设M=5,N=10,且最终的序列为

 
 
  1. 7 2 9 1 5 

由于10(J的最终取值)不在S中出现,所以之前的序列肯定是

 
 
  1. 2 9 1 5 

且RandInt返回值为T=7。又因为9(相应的J的值)出现在4元序列中的2之后,所以之前的T是2。习题4说明了可以类似地恢复出整个随机数序列。由于假定了所有的随机序列是以相同的可能性出现的,于是所有的排列也同样是等概率的。

我们现在可以利用与算法P的相似性来证明算法F2。在算法的每一步,算法F2中的集合S和算法P中的序列S所含的元素是相同的。因此,1~N的每一个M元子集都由M!个随机序列生成,于是它们是等概率的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值