取样问题——《编程珠玑》读书笔记

问题描述:
     给定一个足够大的随机选择函数rand(), 对于[0, n)范围内的数,随机选取m个(m<n),且取出的m个数有序排列(增序或减序)。
 
问题解答:
 1、    一开始拿到问题的时候,我的想法就是从rand()%n中取出m个不同的数。这其中需要解决的问题一个就是如何去重,另一个是排序问题。
     作者的算法参考自《The Art of Computer Programming. Volume2: Seminumerical Algorithms》,考虑方向完全不一样。
     该算法依次考虑整数0,1,2……n-1,并通过一个适当的随机测试对每个整数进行选择。通过按序访问整数,可以保证输出结果的有序性。
     为了理解选择的标准,我们考虑下m=2, n=5的情况。那么我们需要对每个整数的选取概率定为2/5,并且要保证一定能选出2个数出来。
     首先我们会考虑整数0,选择0的概率为2/5,可以通过下面的语句来实现:
               if (rand() % 5) < 2
     不幸的是,我们不能用同样的概率来选择整数1:这样做的话我们从5个整数中选出的整数可能是两个也可能不是两个。因此决策有一些不同:在已经选择0的情况下以1/4的概率选择1, 而在没有0的情况下一2/4的概率选择1(我们来看下选择1的概率是多少:2/5 * 1/4 + 3/5 * 2/4 = 2/5,使我们需要的概率)。一般说来,如果要从r个剩余的整数中选出s个,我们以概率s/r选择下一个数。
     下面是完整的C代码,可参考
  #include <iostream>
  #include <stdlib.h>
  using namespace std;

  int genknuth(int m, int n)
  {
      for (int i = 0; i < n; i++)
      {
          if ((rand() % (n - i)) < m)
          {
              cout << i << "\t";
              m--;
          }
      }
      cout << endl;
      return 0;
  }

  int main()
  {
      genknuth(10, 101);
      return 0;
  }

 

2、顺带附上刚开始的想法,时间复杂度为O(mlogm):
  int gensets(int m, int n)
  {
      set<int> s;
      while (s.size() < m)
      {
          s.insert(rand() % n);
      }
      set<int>::iterator i;
      for (i = s.begin(); i != s.end(); i++)
      {
          cout << *i << "\t";
      }
      cout << endl;
      return 0;
  }

3.

生成随机数另一种方法是把包含整数0~n-1的数组顺序打乱,然后把m个元素排序输出。Ashley Shepherd和Alex Woronow发现,在这个问题中我们只需要打乱数组的前m个元素,时间复杂度为O(n)
  int intcmp(const void* it1, const void* it2)
  {
      return *(int*)it1 - *(int*)it2;
  }
  int randrange(int a, int b)
  {
      return (rand() % (b -a)) + a;
  }
  int genshuf(int m, int n)
  {
      int i, j;
      int* x = new int[n];
      for (i = 0; i < n; i++)
      {
          x[i] = i;
      }
      int t;
      for (i = 0; i < m; i++)
      {
          j = randrange(i, n - 1);
          t = x[i];
          x[i] = x[j];
          x[j] = t;
      }

      qsort(x, m, sizeof(int), intcmp);
      for (i = 0; i < m; i++)
      {
          cout << x[i] << "\t";
      }
      cout << endl;
      return 0;
  }

 

posted on 2012-05-15 11:10  aho  阅读(383)  评论(0编辑  收藏  举报

导航