《编程珠玑》笔记12 随机数生成

1.问题

  抽象后的问题如下:输入两个整数m和n,(m < n).输出0~n-1范围内的m个随机整数的有序列表,不允许重复

  也就是说,要对0~n-1范围内的数字进行选择,每个数字被选中的概率相等.

  有两点要注意:不允许重复,结果有序;
2.解决方案

  2.1已有知识

  利用库函数<stdlib.h>中的rand()函数可以产生0到RAND_MAX范围内的随机整数。

   RAND_MAX是在前面头文件中定义的宏,具体大小与实现有关,至少为32767(2^15-1).

   一般32位机,int型为4字节,故RAND_MAX大小为2147483647(2^31-1).

  两个相关函数:

   产生很大随机整数bigrand():RAND_MAX *rand() + rand();实际上就是先产生前15位,再产生后15位,也即rand()<<15 | rand();

  产生指定范围随机整数 randint(l,u): rand()%(u-l+1)+l; 产生的数据在[l,u]之间.

  (后面对时间复杂度的讨论,都默认rand()需要单位时间)

   2.2方法一:使用set集

  这种方法也是一种较为通用的方法,使用set不仅排除了生成相同的数字,对其遍历的过程也实现了排序。

void getsets(int m, int n)
    {
        set<int> S;
        while(S.size() < m)
            S.insert(rand()%n);      //insert保证了如果出现S中已有数字,那就什么都不做
        set<int>::iterator i;
        for(i = S.begin(); i != S.end(); i++)
            cout << *i << endl;
    }

   时间效率:set每次插入时间为O(logm),while循环总共进行了m次插入,遍历集合需要O(m).故总时间为O(mlogm).额外的空间需求为set集的大小:O(m).

  2.3方法二:Knuth的算法S

  依次考虑整数 0,1,2,……,n-1,通过一个适当的随机测试对每个整数进行选择。通过按序访问整数,保证输出结果有序。

  设m=2, n=5,那么选择第一个整数0的概率就是2/5,这种确定概率,通过 if (rand()%5) < 2 来判断是否选取该数字,然后判断是否选择整数1,若0被选中,以 1/4 的概率选择1, 若0未被选中,以 2/4 的概率选择1,…… ,总之,对于从r个剩余整数中选s个,以 s/r 来选择下一个数。

void getknuth(int m, int n)
    {
        for(int i = 0; i < n; i++)
            if(rand() % (n-i) < m)
            {
                cout << i << endl;
                m--;
            }
    }

时间效率:该程序的时间复杂度是与n有关的:O(n),空间上只需要几十个字节。

2.4方法三:打乱数组顺序

   对于包含整数0~n-1的数组,打乱数组的前m个元素,然后把前m个元素排序输出即可。

 void genshuf(int m, int n)
    {
        vector<int> x(n);
        for(int i = 0; i < n; i++)
            x[i] = i;
        for(int j = 0; j < m; j++)
        {
            int k = randint(i, n-1);
            SWAP(x[i], x[k]);
        }
        sort(x, x+m);
        for(int j = 0; j < m; j++)
            cout << x[j] << endl;
    }

时间效率:时间上为O(n+mlogm), 一次初始化和排序。空间上也需要O(n).

  2.5其他方法

  根据问题的实际情况(m和n的相对大小),如若n为100万,m为n-10时,可以生成10个元素的随机样本,然后输出不在样本中的整数

3.原理

  解决问题的步骤:理解分析问题 —— 建模提出抽象模型 —— 考虑多种解法 —— 实现一种方案 —— 回顾改进

4.习题

  4.8 从0~n-1中随机选择m个数,

    输出顺序随机,不重复:(使用方法2.4,但不进行排序)

    fori = [0,n)
              x[i] = i;
           for j = [0,m)
              int k = randint(i, n-1);
              SWAP(x[i], x[k]);

    for i = [0,m)

      cout << x[i]

    输出顺序随机,可重复:(最普通的情况)

      for i = [0, m)

        cout << rand() % n;

    输出有序,不重复

      前面写的三种方法均可。

    输出有序,可重复

      删除对生成数字是否已在结果集的判断,(可以采用第13章将要介绍的各种数据结构,也可以直接使用multiset标准STL)

posted @ 2012-09-09 19:04  dandingyy  阅读(1975)  评论(0编辑  收藏  举报