http://www.hawstein.com/posts/make-thiner-programming-pearls.html
取样问题
取样问题
问题:对于整数m和n,其中m<n,输出0~n-1范围内m个随机整数的
有序列表, 不允许重复。
比如m=3, n=5,那么一种可能输出是0,2,3(要求有序)。实现1来自Knuth的TAOCP, 时间复杂度O(n):
void GenKnuth(int m, int n) {
    for(int i=0; i<n; ++i) {
        if((bigrand()%(n-i)) < m) {
            cout<<i<<endl;
            --m;
        }
    }
}
其中,bigrand()的作用是返回一个很大的随机整数。
实现2:在一个初始为空的集合里面插入随机整数,直到个数足够。代码如下:
void GenSets(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;
}
实现3:把包含整数0~n-1的数组顺序打乱,然后把前m个元素排序输出。 该方法的性能通常不如Knuth的算法。代码如下:
void GenShuf(int m, int n) {
    int x[n];
    for(int i=0; i<n; ++i)
        x[i] = i;
    for(int i=0; i<m; ++i) {
        int j = randint(i, n-1);
        swap(x[i], x[j]);
    }
    sort(x, x+m);
    for(int i=0; i<m; ++i)
        cout<<x[i]<<endl;
}
(1) 第一种方法来自Knuth的《The art of Computer Programming, Volume 2: Seminumerical Algorithms》
伪代码是:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
 | select = mremaining = nfor I = [0 n )  if (bigrand() % remaining) < select    print i    select—  remaining— | 
只要m<=n,程序选出来的整数就恰为m个。
C++的实现如下:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
 | void genKnuth(int m, int n) {  for( int i = 0; i < n; i++) {   if(bigrand() % (n - i) < m) {    cout <<i << endl;    m--;   }  }} | 
该算法非常节省空间,但需要全部扫描n个数,当n很多时,效率不高。
(2)第二种方法的复杂度只与m有关,采用了set(实际上是红黑树)节省时间。代码如下:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
 | void gensets(int m, int n) {  set<int> S;  while(S.size() < m) {   S.insert(bigrand() % n);  }  // print S} | 
该方法每次插入均在O(log m)时间内完成,但需要的空间开销很大。
(3)第三种方法克服了(2)的缺点,代码如下:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
 | 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] = x;  }  sort(x, x+m);  //print result} | 
该算法需要n个元素的内存空间和O(n+mlogm)的时间,其性能通常不吐Knuth的算法。
(4)当m接近n时,基于集合的算法生成的很多随机数都要丢掉,因为之前的数已经存在于集合中了,为了改进这一点,算法如下:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
 | void genfloyd(int m, int n){  set<int> S;  set<int>::iterator i;  for(int j=n-m; j < n; j++) {   int t = bigrand()%(j+1);   if(S.find(t) == S.end()){   S.insert(t); // t not in S  } else {   S.insert(j); // t in S  } }  //print results} |