• 转:在0~N(不包括N)范围内随机生成一个长度为M(M <= N)且内容不重复的数组


    1. 最朴素暴力的做法.

    void cal1()
    {
        int i = 0, j = 0, num = 0;
        int result[M];
    
        result[0] = rand() % N;    //第一个肯定不重复, 直接加进去
    
        for (i = 1; i < M; i++)    //获得剩下的(M-1)个随机数
        {
            num = rand() % N;    //生成0 ~ N之间的随机数字
    
            for (j = 0; j < i; j++) 
            {
                if (num == result[j]) //如果和result数组中某个元素重复了
                {
                    i--;    //重新开始此次循环
                    break;
                }
            }
    
            if (j == i) // 说明新产生的数和数组里原有的元素都不同, 则add进去
            {
                result[i] = num;
            }
        }
    }

    2. 在方法1的基础上我们可以进行优化. 每轮遍历n太耗时, 那么优化成logn如何, 于是采用一下set作为辅助, 为了利用它logn的时间复杂度. 代价是多了一个M大小的set的空间.

    void cal2()
    {
        set<int> s;
        int num = 0, index = 0;
        int result[M];
    
        while (index < M)
        {
            num = rand() % N;
            if (s.find(num) == s.end())    //如果没找到
            {
                s.insert(num);
                result[index++] = num;
            }
        }
    }

    3. 如果你被方法1和2无穷的重复比较弄烦了, 可能想到, 每次新产生的随机数都要和已有的数去进行比较是否已存在, 越往后这种方法的效率越低, 不停在做无用功. 那么反其道而行如何? 几斤几两咱们都亮在牌面上, OK, 把所有数先都列出来, 从里面往外筛选, 那么每次选出来的, 肯定是不重复的. 只需要选M次就可以了.

    void cal3()
    {
        int result[M] = {0};
        deque<int> deq;<span style="white-space:pre">    </span>//队列
        int i = 0, index;
    
      <span style="white-space:pre">    </span>for (i = 0; i < N; i++) //初始化, 把所有N个数都放到容器里, 从这里面往外挑, 每次必不重复
        {
            deq.push_back(i);
        }
    
        for (i = 0; i < M; i++)    //挑选出M个数
        {
            index = rand() % deq.size(); //注意deq.size()是不断变小的, 但是每次都符合随机特性
            result[i] = deq.at(index);    //把deq数组index位置的元素赋给result[i]
            deq.erase(deq.begin() + index);    //从deq队列中把该元素删除
        }
    }

    4. 方法3是思路比较理想化和直接, 实际操作中, 你会发现比方法1都要慢很多很多, 原因就出在容器的erase函数, 内部实现的本质是内存片的拷贝, 这个操作相当相当的耗时.这个和语言无关, 换成java等其它语言, 类似的这种函数都是同样的原理. 其实思考到方法3, 真理已经呼之欲出了. 我们沿着这个思路继续优化算法. 从中剔除元素的想法是好的, 但是方法不佳. 其实我们需要的本来就是基本的数组就可以了, 速度还快, 用deque, vector这些容器无非是为了使用他们的erase函数把某个数剔除出去不参与下次的随机过程. 随着一个个数被选出, 容器的大小也在不停变小, 其实使用数组, 利用下标的偏移, 我们直接就可以做到了! 和用数组实现一个队列或者栈不是一样的吗, 无非就是数组下标的移动! 于是沿着方法3的思想, 我们每次随机出来一个下标index(0 <= index < size(size初值为N)), 每次把arr[index]这个位置的元素甩到数组最后面就可以了, 就相当于剔除操作了!

    void cal4()
    {
        int result[M] = {0};
        int data[N] = {0};
        int i = 0, index = 0;
    
        for (i = 0; i < N; i++)    //初始化
        {
            data[i] = i;
        }
    
        for (i = 0; i < M; i++)
        {
            index = rand() % (N - i);
            result[i] = data[index];    //把data数组index位置的元素赋给result[i]
            data[index] = data[N - i - 1];    //从data数组末尾(这个位置在不停前移)拿一个数替换到该位置, 相当于这个元素被剔除了
        }
    }

    性能测试:

    http://blog.csdn.net/aa2650/article/details/12507817

    直接输出:

    如何用随机数生成0到n之间的m个不重复的数

    1、最直接的方法就是先随机生成一个0到n之间的数,判断这个数是否已被选上,如果以前没选过,则选上,如果以前已选,则丢弃

    [cpp] view plaincopy
     
    1. void common(int n,int m)  
    2. {  
    3.     int * randnum=(int *)malloc(n*sizeof(int));  
    4.     memset(randnum,0,n*sizeof(int));   //把n个位置全部置0  
    5.     srand(time(NULL));  
    6.     while(m)  
    7.     {  
    8.         int cur=rand()%n;  
    9.         if (randnum[cur]==0)   //进行判断,如果当前数没有选择过,则选择并输出  
    10.         {  
    11.             cout<<cur<<endl;  
    12.             randnum[cur]=1;  
    13.             m--;  
    14.         }  
    15.     }  
    16.     free(randnum);  
    17.   
    18. }  


    这种方法简单易懂,但是需要额外的空间来确保取出的数不重复,那么我们有没有更为简单的方法呢,答案是肯定的

    2、先上代码,后做解释

    [cpp] view plaincopy
     
    1. void mRand(int n ,int m)  
    2. {  
    3.   
    4.     srand(time(NULL));  
    5.     for (int i=0;i<n;i++)  
    6.     {     
    7.         if(rand()%(n-i)<m)  
    8.         {  
    9.             cout<<i<<endl;  
    10.             m--;  
    11.         }  
    12.           
    13.     }  
    14. }  


    上边的代码虽然简洁,但是不易懂,我们接下来说明一下

    首先是一个循环,这个循环确保了输出的数是不重复的,因为每次的i都不一样

    其次是m个数,在每次循环中都会用rand()%(n-i)<m来判断这个数是否小于m,如果符合条件则m减1,直到为0,说明已经取到m个数了

    再次是如何保证这m个数是等概率取到的

    在第一次循环中i=0, n-i=n, 则随机数生成的是0-n-1之间的随机数,那么此刻0被取到的概率为 m/n-1
    在第二次循环中i=1,n-i=n-1,则随机数生成的是0-n-2之间的随机数,这时1被取到的概率就和上一次循环中0有没有取到有关系了。假设在上一次循环中,没有取,则这次取到的1的概率为 m/n-2;假设上一次循环中,已经取到了,那么这次取到1的概率为m-1/n-2,所以总体上这次被取到的概率为 (1-m/n-1)*(m/n-2)+(m/n-1)*(m-1/n-2),最后通分合并之后的结果为m/n-1和第一次的概率一样的
    同理,在第i次循环中,i被取上的概率也为m/n-1

    所以这m个数是等概率取到的

    参考:http://blog.csdn.net/dlengong/article/details/7932579

  • 相关阅读:
    Spring 中众多的的Initializer
    Spring Factories
    spring 的各种context
    @import和@Bean的区别,以及ImportSelector和ImportBeanDefinitionRegistrar两个接口的简单实用
    Spring 配置的方式
    记一次JAVA FULL GC问题处理【3】
    关于String, StringBuilder,StringBuffer 的一些测试数据
    记一次JAVA FULL GC问题处理【2】
    ThreadLocal 结构
    记一次JAVA FULL GC问题处理【1】
  • 原文地址:https://www.cnblogs.com/youxin/p/4330421.html
Copyright © 2020-2023  润新知