问题描述:程序的输入包含两个整数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;
}