问题:程序输入两个整数m和n,其中m<n。输出是0~n-1范围内的m个随机整数的有序列表,不允许重复,要求每个选择出现的概率相等。
(1)扫描算法:扫描0~n-1,通过一个概率测试来对每个整数进行选择,可保证输出结果是有序的。考虑m=2,m=5的情况。选择的0的概率为2/5,通过语句if(bigrand()%5)<2来实现。若选择了0,则在剩下的4个数中以1/4的概率选择一个数。若未选择0,则以2/4的概率选择下一个数,这样就能保证恰好能选出m个整数。这里bigrand()返回一个很大的随机整数,远大于m和n。一般来说,如果要从r个剩余整数中选择s个,我们以概率s/r选择下一个整数。
显然,算法的运行时间为O(n)。
(2)基于集合的算法:在一个初始为空的集合里面插入随机整数,直到个数足够,核心问题是如何实现集合S。我们可以考虑有序链表、二叉树等数据结构,但最直接的办法是利用C++标准模板库中的set容器。
C++ STL规范要求每次插入操作都在O(logm)时间内完成,而遍历集合则需要O(m)时间。因此整个程序需要O(mlogm)时间(当m相对于n较小时)。但是该数据结构的空间开销比较大。
当n比较大而m接近于n时,基于集合的算法需要生成大量的随机数,而其中很多随机数都要丢掉(因为之前集合中已经存在这个数了),这导致很多不必要的运行时间开销。Bob Floyd给出了一个算法,使得即使在最坏情况下也只使用m个随机数。如下(C++实现):
(3)排序算法:生成随机整数的有序子集的另一种方法是把包含整数0~n-1的数组顺序打乱,然后把前m个元素排序输出。
算法需要n个元素的内存空间和O(n+mlogm)的时间。
关键算法设计思想:扫描策略、概率测试(随机数生成)、集合数据结构、搜索策略、排序策略。