zoukankan      html  css  js  c++  java
  • 《编程珠玑》阅读小记(9) — 取样问题

    问题

    本章研究的问题是取样问题,也就是程序设计中的随机数,问题描述如下:
    程序的输入包含两个整数m和n,其中 m < n;输出是0~n-1范围内m个随机整数的有序列表,不允许重复。从概率的角度看,我们希望没有重复的有序选择,其中每个选择出现的概率相等。
    条件假设:
    我们假设有一个能返回很大的随机整数(远远大于m 和 n )的函数bigrand(),以及一个能返回i…j范围内均匀选择的随机整数的randint(i,j)。
    本章关于这个问题提供了三种算法,接下来详细叙述每个算法的程序实现。

    算法1

    该算法依次考虑0,1,2,…,n-1 , 并通过一个适当的随机测试对每个整数进行选择。通过按序访问整数,我们可以保证输出结果是有序的。
    代码实现如下:

    /************************************************************************/
    /* 《编程珠玑》第十二章 取样问题
     * 问题:程序的输入包含两个整数m和n,其中m<n。输出是0~n-1范围内m个随机整数的有序列表,不允许重复
     * 方案一
     */
    /************************************************************************/
    
    #include <iostream>
    #include <algorithm>
    #include <cstdlib>
    
    using namespace std;
    
    /************************************************************************/
    /* 返回一个很大的随机整数(远大于m和n)                                 */
    /************************************************************************/
    int bigRand()
    {
        return RAND_MAX * rand() + rand();
    }
    
    /************************************************************************/
    /* 返回一个位于l与u之间的均匀选择的随机整数                             */
    /************************************************************************/
    int randint(int l , int u)
    {
        return l + bigRand() % (u - l + 1);
    }
    
    /************************************************************************/
    /* 解决问题的算法1                                                      */
    /************************************************************************/
    void rand1(int m, int n)
    {
        int select = m, remaining = n;
        for (int i = 0; i < n; i++)
        {
            if ((bigRand() % remaining) < select)
            {
                cout << i << "	";
                select--;
            }
            remaining--;
        }
        cout << endl;
    }
    
    int main()
    {
        int m = 5, n = 10;
        while (cin >> n >> m)
        {
            rand1(m, n);
        }
    
        system("pause");
        return 0;
    }

    对于该算法,只要m<=n,程序选出的整数就恰好为m个,不会选择更多的整数,因为select变为0的时候,就不能选择整数了;也不会选择更少的整数,因为select/remaining为1的时候,一定会选中一个整数。以上代码中,我们可以看出,每个子集被选中的概率是相同的。
    对于该算法,程序实现只需要占用几十个字节的内存,而且可以快速解决问题。但是,当n很大的时候,代码运行就是相对较慢。

    算法2

    我们知道,C++标准程序库中的集合set有两个重要性质,集合内元素不重复,集合内元素有序排列(默认升序)。对于求随机数的问题,可以利用set的性质,向一个初始为空的set中插入随机整数知道数量达到要求为止。
    算法实现如下:

    /************************************************************************/
    /* 《编程珠玑》第十二章 取样问题
    * 问题:程序的输入包含两个整数m和n,其中m<n。输出是0~n-1范围内m个随机整数的有序列表,不允许重复
    * 方案二
    */
    /************************************************************************/
    
    #include <iostream>
    #include <algorithm>
    #include <cstdlib>
    #include <set>
    
    using namespace std;
    
    /************************************************************************/
    /* 返回一个很大的随机整数(远大于m和n)                                 */
    /************************************************************************/
    int bigRand()
    {
        return RAND_MAX * rand() + rand();
    }
    
    /************************************************************************/
    /* 返回一个位于l与u之间的均匀选择的随机整数                             */
    /************************************************************************/
    int randint(int l, int u)
    {
        return l + bigRand() % (u - l + 1);
    }
    
    /************************************************************************/
    /* 解决问题的算法2                                                     */
    /************************************************************************/
    void rand2(int m, int n)
    {
        set<int> s;
        while (s.size() < m)
        {
            s.insert(bigRand() % n);
        }
    
        set<int>::iterator iter;
        for (iter = s.begin(); iter != s.end(); iter++)
            cout << *iter << "	";
        cout << endl;   
    }
    
    int main()
    {
        int m = 5, n = 10;
        while (cin >> n >> m)
        {
            rand2(m, n);
        }
    
        system("pause");
        return 0;
    }

    C++标准模板库规范每次插入操作都在O(logm)的时间内完成,而遍历集合则需要O(m)时间,因此完整的程序需要O(mlogm)时间。但是该数据结构空间开销比较大。

    算法3

    生成随机整数的有序子集的另一种方法是把包含整数0~n-1的数组顺序打乱,然后把前m个元素排序输出。
    算法实现如下:

    /************************************************************************/
    /* 《编程珠玑》第十二章 取样问题
    * 问题:程序的输入包含两个整数m和n,其中m<n。输出是0~n-1范围内m个随机整数的有序列表,不允许重复
    * 方案三
    */
    /************************************************************************/
    
    #include <iostream>
    #include <algorithm>
    #include <cstdlib>
    
    using namespace std;
    
    /************************************************************************/
    /* 返回一个很大的随机整数(远大于m和n)                                 */
    /************************************************************************/
    int bigRand()
    {
        return RAND_MAX * rand() + rand();
    }
    
    /************************************************************************/
    /* 返回一个位于l与u之间的均匀选择的随机整数                             */
    /************************************************************************/
    int randint(int l, int u)
    {
        return l + bigRand() % (u - l + 1);
    }
    
    /************************************************************************/
    /* 解决问题的算法3                                                      */
    /************************************************************************/
    void rand3(int m, int n)
    {
        int *x = new int[n];
        for (int i = 0; i < n; i++)
        {
            x[i] = i;
        }
    
        for (int i = 0; i < m; i++)
        {
            int j = randint(i, n - 1);
            int t = x[i];
            x[i] = x[j];
            x[j] = t;
        }
    
        sort(x, x + m);
    
        for (int i = 0; i < m; i++)
            cout << x[i] << "	";
        cout << endl;
    }
    
    int main()
    {
        int m = 5, n = 10;
        while (cin >> n >> m)
        {
            rand3(m, n);
        }
    
        system("pause");
        return 0;
    }

    上述算法需要n个元素的存储空间,以及O(n+mlogm)的运行时间。

    原理

    本章示例了编程过程中的几个重要步骤,在实际应用的算法设计以及程序实现时,我们必须遵循以下原理:

    • 正确理解所遇到的问题
    • 提炼出抽象问题,简洁、明确的问题陈述不仅可以帮助我们解决当前遇到的问题,还有助于我们把解决方案应用到其他问题中;
    • 考虑尽可能多的解法,非正式的高级语言可以帮助我们描述设计方案:伪代码表示控制流,抽象数据类型表示关键的数据结构。
    • 实现一种解决方案
    • 回顾
  • 相关阅读:
    05 单例模式
    04 volatile关键字实现原理
    03 synchronized
    02 java内存模型
    spark性能调优06-数据倾斜处理
    redis缓存架构-03-redis下的replication以及master+slave
    SpringBoot webSocket搭建示例
    Httpclient-(get、post(application/json)、post(application/form-data)、download、upload)
    CentOS-Java 依赖安装
    Git使用教程
  • 原文地址:https://www.cnblogs.com/shine-yr/p/5214944.html
Copyright © 2011-2022 走看看