• 疯狂位图之——位图扩展随机数


      这篇文章主要介绍随机数,不过这次讲述的随机数是从上两篇的延伸出来的,所以还是继续疯狂位图的标题,欢迎看完本文拍砖。

    一、实现需求

      利用C语言提供的rand()函数构造一个随机函数randLong(),能够生成0-(2^31-1)之间的随机整数。rand()生成的随机整数在0-(2^15-1)范围内。可以参考上一篇的位图生成数据。

    二、利用位图的方法

      起初我以为很简单:

    void randLong(){
        return 1.0*rand()/RAND_MAX*LONG_MAX;
    }

      后来受五岳的文章启发,发现我犯了严重的错误,rand()的取值空间中只有2^15-1=32767个整数,按上面的方法只是在0-(2^31-1)上抽取了一小部分数。如果说rand()生成的是0-32767直接的随机浮点数而不是整数的话,上面的办法扩展随机数的范围是可行的。

      因为一直在整位图,所以在想能不能从位入手,实现随机数的扩展。我都有点感受到位图的强大了,还真想出了一个用位图扩展的方法,这里不再拐弯抹角,直接上菜:

      需要生产的随机数是[0-(2^31-1)]的闭区间内,端点值化成2进制后为:

    0: 0000,0000,0000,00000000,0000,0000,0000
         ...
         ...
         ...
    2^31-1:0111,1111,1111,1111,1111,1111,1111,1111 

      看到这个二进制后,相信你已经有想法了。我们申请一个32位长整型的位图,将位图每一位初始化为0,这时候我们需要一个rand1()生产0,1的随机数,对每个位调用一次random=rand1(),如果random=1就把该位设置为1,如果为random=0则设置为0,调用31次(除最高位为0不需要随机选择)后,这个长整型变量的值就是我们需要的随机数。

      如果rand1()足够随机,即等概率(1/2)的生成0、1,那么我们按上面的方法的得到的任意一个数的概率都为(1/2)^31。来看一下实现代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    
    /*******生成0、1的随机产生器********/
    short rand01(){
        return rand() % 2;
    }
    /********将bit所在位设置为1**********/
    void setOne(long * p, short bit){
        *p |= 1<<bit;
    }
    /********将bit所在位设置0**********/
    void setZero(long *p, short bit){
        *p &=~(1<<bit);
    }
    /*******生成0-2^bitNum-1之间的随机数**************/
    long randLong(short bitNum){
        long a = 0;
        long *p = &a;    
        for(short i= 0;i < bitNum; i++){
            short bit = rand01() % 2;
            if(bit == 0)
                setZero(p,i);
            else
                setOne(p,i);
        }
        return *p;
    }

      randLong(short bitNum)可以生成0到2的任意幂次方之间的随机数(当然bitNum<=31),生成的随机数基本上等概率的。我们可以测试一下生成0-7的随机数概率:

    void main(){
        const int max = 8;
        int count[max];
        for(short i=0;i<max;i++)
            count[i] = 0;
        long num=10000000;    
        for(long i=0;i<num;i++){
            long index = randLong(3);//生成0-(2^3-1)的随机数
            count[index]++;//计数    
        }
        for(short i=0;i<max;i++)
            printf("%d:	%lf
    ",i,1.0*count[i]/num);//输出概率    
    }

      输出的结果如下: 

      进行了1000万次试验,发现每个数的概率还是很接近1/8=0.125的。

    三、递归实现的方法

      上面有位图的方法,每次生成一个随机数需要调用多次rand01(),速度略微慢些,这里再提供一个基于递归的实现方法,直接上代码:

    /**********根据rand()构造[0,max]的闭区间的随机整数**********/
    long random(long max){
        if(max <= RAND_MAX)//RAND_MAX是rand()的最大值,值为0x7fff
            return rand()% (max+1);//直接得到[0,max]的随机数
        else
            return (random(max/(1+RAND_MAX)) * (1+RAND_MAX) + rand()) % (max+1);//递归得到[0,max]的随机数
    }

        当max<=RAND_MAX时,对rand()模余就得到了[0,max]的随机数;

      当max>RAND_MAX时,我们先来看 (random(max/(1+RAND_MAX)) * (1+ RAND_MAX) + rand()) ,令k=max/(1+RAND_MAX),即k表示max是(1+RAND_MAX)的倍数向下取整,前面的式子变为(random(k) * (1+RAND_MAX)+ rand())。这里为了方便说明,我们假设rand()生成[0,4]的整数(即RAND_MAX=4),max=52,于是k=52/(1+4)=10,相当于把[0,52]分成10个区间,根据定义random(10)生成的是[0,10]的整数,random(10)*5相当于随机的定位到每个区间的开始位置,再加上一个rand(),即在左端点上加一个随机的偏移量构成一个新的随机数,而10依然大于4,需要用同样的方法生成random(10)。可以结合图来理解:  

      最后还需要将(random(10) * 5+ rand())对53模余,因为两个相加的结果可能会大于52,模余53可以保证生成的数在[0,52]范围内,注意是模余53不是52。

      有了上面的方法我们可以进一步改进,生成任意区间上的随机整数,代码如下:

    /*******根据c语言提供的rand()生成long型的任何区间随机整数值*******/
    long random(long min,long max){
        if(max <= RAND_MAX)
            return min + (rand()%(max - min + 1));
        else
            return min + ((random(max/(1+RAND_MAX)) * (1+RAND_MAX) + rand()) % (max-min+1));
    }

       我们来测试一下区间随机数:

    void testRandom(){
        long min = 234567;
        const short num = 9;
        long count[num+1];
        for(short i=0;i<=num;i++){
            count[i]=0;
        }
        long testTimes = 10000000;//试验次数
        for(long i=0;i<testTimes;i++)
            count[random(min,min+num)-min]++;//计数器
        for(short i=0;i<=num;i++)
            printf("%d:	%lf
    ",i+min,1.0*count[i]/testTimes);//输出概率    
    }

     生成了[234567,234567+9]之间的10个数,运行的结果如下:

      每个数出现的概率非常接近1/10=0.1,可以认为是等概率生成。

      前面我们说过,模余可以保证生成的数不超过范围内,但会造成概率不相等的情况,特别是在用大的rand()生成小的随机数时,我们测试一下,用rand7()构造rand4(),rand7()生成[0,7]的整数,rand4()生成[0,4]的整数,看如下代码:

    int rand7(){//生成0到7的整数
        return rand()%8;
    }
    int random4(){//生成0到4的整数
        return rand7() % 5;
    }
    void main(){
        const int max = 5;
        int count[max];
        for(short i=0;i<max;i++)
            count[i] = 0;
        long num=10000000;    
        for(long i=0;i<num;i++){
            count[random4()]++;//计数    
        }
        for(short i=0;i<max;i++)
            printf("%d:	%lf
    ",i,1.0*count[i]/num);//输出概率
    }

      输出的结果如下:

      

      我们可以看到进行1000万次试验,生成0、1、2的概率约为0.25=1/4,而3、4的概率约为0.125=1/8,这两组之间的概率相差一倍,为什么会这样?稍加分析,我们就知道了,rand7()生成的数为[0,1,2,3,4,5,6,7],且是等概率的生成这8个数,模余5后的结果为[0,1,2,3,4,0,1,2],所以rand4()中的0,1,2即有可能来自[0,1,2,3,4,5,6,7]中的0,1,2,也可能来自5,6,7,而rand4的3,4只能由rand7()的3,4而来。所以会出现0,1,2的概率是3,4概率的两倍,一般的测试实验可以不用考虑,在需要严格的随机测试实验时,应当注意这个问题。用到模余的地方都有可能造成概率不等的情况,要想写一个通用的概率相等的random(m,n)是一个数学难题。

      挂羊头卖狗肉了,这篇主要讲述随机数的一些东西,也牵扯到了些位图。

      补充:后来想到用位运算扩展C语言的rand()到long范围内的randLong(),其实可以用位移运算,只需要调用rand()就可以了,看代码:

    /*********通过移位来构造长整型的随机数***************/
    long randLong(){
        long r = (long)rand();
        return (r << 16)+ rand();
    }

       这个比调用31次都rand01()快很多,而且只要rand()是等概率的,生成的randLong()也是等概率的。

      感谢关注,欢迎评论。

      转载请注明出处:http://www.cnblogs.com/fengfenggirl

      

  • 相关阅读:
    Android实现通过浏览器点击链接打开本地应用(APP)并拿到浏览器传递的数据(转)
    保存图片文件到本地
    android ScrollView中嵌套GridView,ListView只显示一行的解决办法
    蒙版提示页(添加新功能后的一种提示)
    C和指针 第三章--数据
    *(ptr++) += 123
    优先级队列-堆
    单链表相关(一)
    字符间的距离-动态规划
    和最大的连续子数组
  • 原文地址:https://www.cnblogs.com/fengfenggirl/p/bit_random.html
Copyright © 2020-2023  润新知