问题描述:程序的输入包含两个整数m和n,其中m<n。输出是0~n-1范围内m个随机整数的有序列表,不允许重复。从概率的角度说,我们希望得到没有重复的有序序列,其中每个选择出现的概率相等。
讨论这些问题前有两个假设:1.有一个函数bigrand()能够返回很大的随机整数(远远大于m和n)。
2.另一个函数randint(i,j)能够返回i..j范围内均匀选择的随机整数。
解法一:假设m=2,n=5,选择第一个数0的概率为2/5,可以通过以下语句来实现:if(bigrand()%5)<2,<2也就是只能取0或者1,表示了概率为2/5。但是当我们选择1的时候,要根据前面是否选择了0。如果选择了0,则按照1/4的概率选择1,而未选择0的情况下按照2/4的概率选择1,以此类推。当m=n的时候,一定能够被选中(因为bigrand()%m<n(m)一定是成立的)。
select=m remaining=n for i=[0,n) if(bigrand()%remaining)<select print i select-- remaining--分析:这里我们是遍历[0,n),然后用概率选择,如果选中了select和remaining都要减1,如果没有选中remaining减1。当select减少到0的时候程序虽然在运行,但是已经不可能再选中了,所以可以修改一下程序当select减少为0的时候跳出。Knuth给出了证明,每个子集被选中的可能性是相等的。
c++实现:
void getknuth(int m,int n){ for(int i=0;i<n;i++) if((bigrand()%(n-i))<m){ cout<<i<<"\n"; m--; } }
解法二:在一个初始为空的集合里面插入随机整数,直到个数足够。我们这里使用c++ stl中的set来实现。
set是一个容器,它其中包含的元素值是唯一的。比如我们以下面这段程序说明:
#include<iostream> #include<set> using namespace std; int main(){ set<int> s; s.insert(3); s.insert(3); cout<<s.size(); return 0; }输出的值是1,若相同的话set选择不插入。
void getsets(int m,int n){ set<int> S; while(S.size()<m) S.insert(bigrand()%n); set<int>::iterator i; for(i=S.begin();i!=S.end();++i) cout<<*i<<endl; }解法三:把包含0~n-1的数组顺序打乱,然后把前m个元素排序输出。
void genshuf(int m,int n){ int i,j; int *x=new int[n]; for(i=0;i<n;i++) x[i]=i; for(i=0;i<m;i++){ j=randint(i,n-1); int t=x[i]; x[i]=x[j]; x[j]=t; } sort(x,x+m); for(i=0;i<m;i++) cout<<x[i]<<endl; }