• 概率与随机函数问题


    首先我们来一道最简单的题目作为引子
    1、已知有一个随机函数rand_0_and_1_with_p(),它能以概率p产生0,以概率1 - p产生1,只使用该函数,设计一新的随机函数,要求以等概率产生1和0。
    我们知道,运行rand_0_and_1_with_p()函数一次,那么P(0) = p, P(1) = 1 - p。那么如果运行两次的话,P(0 and 1) = p(1 - p),P(1 and 0) = p(1 - p),这样就出现了等概率,所以我们可以如下实现:

    int rand_0_and_1_with_equal_prob() {
        int tmp1 = rand_0_and_1_with_p();
        int tmp2 = rand_0_and_1_with_p();
        if (tmp1 == 1 && tmp2 == 0) {
            return 1;
        } else if (tmp1 == 0 && tmp2 == 1) {
            return 0;
        } else {
            return -1; 
        }
    }

    注意到,因为题目只是说等概率生成1和0,并没有要求P(1) = 0.5, P(0) = 0.5,所以上述实现是合理的,并且保证了性能,不过实用性不大。那么如果要求该随机函数只能产生0和1,并且等概率呢?其实只要在上述实现中加个循环即可:

    int rand_0_and_1_with_equal_prob() {
        while (1) {
            int tmp1 = rand_0_and_1_with_p();
            int tmp2 = rand_0_and_1_with_p();
            if (tmp1 == 1 && tmp2 == 0) {
                return 1;
            } else if (tmp1 == 0 && tmp2 == 1) {
                return 0;
            } 
        }
    }

    ok,现在又有新的要求了。
    2、已知有一个随机函数rand_0_and_1_with_p(),它能以概率p产生0,以概率1 - p产生1,只使用该函数,设计一新的随机函数,要求以等概率1/n产生1到n之间的随机数。
    其实这个问题可以这么想,我们先用rand_0_and_1_with_p()来实现一个以等概率0.5产生1和0的新函数,见上 rand_0_and_1_with_equal_prob()。有了这个函数,我们不妨考虑数字i的二进制,它只有0和1组成,那么我们每次生成一个0 或者1,生成log2n次就可以以等概率生成数字i了。代码如下:

    int rand_0_to_n_minus_1_with_equal_prob(int n) {
        int k = 0;
        while (n) {
            k++;
            n >>= 1;
        }
        do {
            int res = 0;
            for (int i = 0; i < k; ++i) {
                res |= rand_0_and_1_with_equal_prob() << i;
            }
        } while (res >= n);
        return res;
    }

    这里有个细节需要注意,就是运行log2n次rand_0_and_1_with_equal_prob()函数,最终产生的数可能比n大,因为有可能是如下这种情况:n = 101b,而最后产生的数字是111b,则应该舍弃这种情况,如代码中所示。
    3、给定函数rand5(),它能等概率随机产生1~5之间的数字,要求据此实现rand7(),使得能等概率产生1~7之间的数字。
    这个题目个人感觉非常棒,可以从题2中获得一定的灵感,题2中是将n表示成2进制,那是因为已知的随机函数是产生0和1的,对于该题,一直的随机函数是随机产生1~5的,我们可以很容易的将该函数转化成随机产生0~4,然后再将7表示成5进制的数,则1=015, 2=025, 3=035, 4=045, 5=105, 6=115, 7=125。不过这里我们同样是生成所有两位的五进制数,那么最高是445,即24,然后去掉21,22,23,24剩下的21个数0~20模7正好可以等概率生成0~6,然后加1即可。代码如下:

    int rand7() {
        do {
            int k = 5 * (rand5() - 1) + rand5() - 1;
        } while (k >= 21);
        return (k % 7) + 1;
    }

    注意上面的代码第3行不能直接写成int k = 5 * (rand5() - 1)。
    4、假设已知randn()可以等概率的产生0~n-1的值,现在要求设计一个randm要求等概率产生0~m-1的值。
    该题可看成是rand5和rand7的扩展,同样的思路:
    如果n >= m,则直接取randn()结果的0~m-1即可;
    如果n < m,则可以先判断m可以表示成多少位的n进制数,然后再采用类似上面的算法;
    代码如下:

    int randm(int n, int m) {
        int res = 0;
        if (m <= n) {
            do {
                res = randn();
            } while (res >= m); 
            return res;
        }
    
        int count = 1;
        int tmp = n - 1;
        while (tmp < m) {
            tmp = tmp * n + n - 1;
            count++;
        }
        int times = (tmp / m) * m;
    
        do {
            res = randn();
            for (int i = 0; i < count; ++i) {
                res = res * n + randn();
            }
        } while (res >= times);
    
        return res % m;
    }

    可以写简单的程序验证一下结果即可。
    5、如何产生如下概率的随机数?0出1次,1出现2次,2出现3次,n-1出现n次?
    我们注意到有如下规律:n - 1 = (n - 1) + 0 = (n - 2) + 1 = (n - 3) + 2 = ... = 2 + (n - 3) = 1 + (n - 2) = 0 + (n - 1)
    可以发现,满足a + b = n - i的(a, b)数对的个数为n - i + 1个。所以我们得到如下代码:

    int Rand(int n) {
        while (1) {
            int tmp1 = rand() % n;
            int tmp2 = rand() % n;
            if (tmp1 + tmp2 < n) {
                return tmp1 + tmp2; 
            }   
        }
    }

    参考: http://www.cppblog.com/myjfm/archive/2012/09/10/190092.html

    概率问题

    1、一个随机数产生器以概率p生成0,以概率(1-p)生成1,怎样生成等概率的0和1?

    如果用这个随机数产生器产生两个位,出现00的概率为,出现01的概率为,出现10的概率为,出现11的概率为。看到没有,出现01和10的概率相等。那么我们就可以用这个随机数生成器每次产生2位,直到产生的是01或者10,当为01时,输出0,当为10时输出1。

    问题扩展:还是给这么一个随机数产生器,要求等概率地产生

    解法1:每次产生n位,当为仅第一位是1,其他是0时输出1,当仅有第二位是1,其他位是0是输出2,……当仅有第n位是1,其他是0时输出n。(这个解法有点0疼,好多信息都浪费了,复杂度太高了)

    解法2:从原问题的解答中,我们可以成功地得到等概率产生0和1的,求一下n-1二进制表示的位数,比如为k。那么我们每次等概率生成k位二进制数,将这k位二进制组装成一个数,如果这个数在0~n-1的范围内,输入这个数+1,否则重复产生。

    2、不重复随机数的产生

    我们可以把这题稍微细化一点,随机产生0~n-1中的k个不重复的随机数。

    解法1:最容易想到的方法就是,用一个集合来装这些随机产生的数,当这个集合的大小不为k的时候,就没完没了的随机产生并add到集合中。真正这么干过的童鞋会发现这个办法是不行的,当k稍微大一点,这个程序就跑步出来了。

    解法2:开辟一个长度为n的数组a,向其中装入0~n-1,然后随机产生一个0~n-1的数x,将a[x]与a[n-1]交换,并将a[n-1]输出,接下来随机产生一个0~n-2的数y,将a[y]与a[n-2]交换,并将a[n-2]输出,知道输出了k个数为止……代码实现参考这里

    3、给定能随机生成整数1到5的函数,写出能随机生成整数1到7的函数

    解法1:

    产生K个数(k>1) 假定产生的数分别为n1,n2,n3,n4...那么定义产生的数为n1-1+(n2-2)*5+(n3-1)*5^2+ (n4-1)*5^3........于是产生的数位于区间(0,5^k-1)。然后把5^k分成k等分,产生的数位于哪个等分就是那个产生的随机数(0~6),然后+1即可。如果位于k等分的余数范围,则重新执行一次上述过程。不用担心余数问题,当k取3时落到余数范围的概率就已经降低为 6/125,而且余数不会导致概率的问题,只是会影响效率。(相当于5进制)

    解法2:

    通过rand5()*5+rand5()产生6 7 8 9 10 11......26 27 28 29 30这25个数,每个数出现的概率相等,去前面3*7个数,舍弃后面的4个数,将6 7 8转化成1, 9 10 11转化成2,……,公式:(a-3) / 3,其实跟解法1是一样的,包装一下而已。

    4、如何随机选取1000个关键字

    给定一个数据流,其中包含无穷尽的搜索关键字(比如,人们在谷歌搜索时不断输入的关键字)。如何才能从这个无穷尽的流中随机的选取1000个关键字?

    定义长度为1000的数组。对于数据流中的前1000个关键字,显然都要放到数组中。对于数据流中的的第n(n>1000)个关键字,我们知道这个关键字被随机选中的概率为 1000/n。所以我们以 1000/n 的概率用这个关键字去替换数组中的随机一个。这样就可以保证所有关键字都以 1000/n的概率被选中。对于后面的关键字都进行这样的处理,这样我们就可以保证数组中总是保存着1000个随机关键字。

    PS:关于1000/n的概率问题,可以这样来,随机生成1~n的数,如果是在1~1000内那么就替换相应位置

    5、在半径为1的圆中随机选取一点

    解法1:

    假设圆心在(0,0)。在x轴[-1, 1],y轴[-1, 1]的正方形内随机选取一点。然后判断此点是否在圆内(通过计算此点到圆心的距离)。如果在圆内,则此点即为所求;如果不在,则重新选取直到找到为止。正方形的面积为4,圆的面积为pi,所以正方形内的随机点在圆内的概率是 pi / 4。

    解法2:

    从[0, 2*pi)中随机选一个角度,对应于圆中的一条半径,然后在此半径上选一个点。但半径上的点不能均匀选取,选取的概率应该和距圆心的长度成正比,这样才能保证随机点在圆内是均匀分布的。

    关于不均匀选取问题:在一个直角三角形(斜边长pi,直角边长为1)斜边上随机选一点,然后投影到长为1的直角边应该满足条件。

    参考:

    1、一个随机数产生器以概率p生成0,以概率(1-p)生成1,怎样生成等概率的0和1?

    如果用这个随机数产生器产生两个位,出现00的概率为,出现01的概率为,出现10的概率为,出现11的概率为。看到没有,出现01和10的概率相等。那么我们就可以用这个随机数生成器每次产生2位,直到产生的是01或者10,当为01时,输出0,当为10时输出1。

    问题扩展:还是给这么一个随机数产生器,要求等概率地产生

    解法1:每次产生n位,当为仅第一位是1,其他是0时输出1,当仅有第二位是1,其他位是0是输出2,……当仅有第n位是1,其他是0时输出n。(这个解法有点0疼,好多信息都浪费了,复杂度太高了)

    解法2:从原问题的解答中,我们可以成功地得到等概率产生0和1的,求一下n-1二进制表示的位数,比如为k。那么我们每次等概率生成k位二进制数,将这k位二进制组装成一个数,如果这个数在0~n-1的范围内,输入这个数+1,否则重复产生。

    2、不重复随机数的产生

    我们可以把这题稍微细化一点,随机产生0~n-1中的k个不重复的随机数。

    解法1:最容易想到的方法就是,用一个集合来装这些随机产生的数,当这个集合的大小不为k的时候,就没完没了的随机产生并add到集合中。真正这么干过的童鞋会发现这个办法是不行的,当k稍微大一点,这个程序就跑步出来了。

    解法2:开辟一个长度为n的数组a,向其中装入0~n-1,然后随机产生一个0~n-1的数x,将a[x]与a[n-1]交换,并将a[n-1]输出,接下来随机产生一个0~n-2的数y,将a[y]与a[n-2]交换,并将a[n-2]输出,知道输出了k个数为止……代码实现参考这里

    3、给定能随机生成整数1到5的函数,写出能随机生成整数1到7的函数

    解法1:

    产生K个数(k>1) 假定产生的数分别为n1,n2,n3,n4...那么定义产生的数为n1-1+(n2-2)*5+(n3-1)*5^2+ (n4-1)*5^3........于是产生的数位于区间(0,5^k-1)。然后把5^k分成k等分,产生的数位于哪个等分就是那个产生的随机数(0~6),然后+1即可。如果位于k等分的余数范围,则重新执行一次上述过程。不用担心余数问题,当k取3时落到余数范围的概率就已经降低为 6/125,而且余数不会导致概率的问题,只是会影响效率。(相当于5进制)

    解法2:

    通过rand5()*5+rand5()产生6 7 8 9 10 11......26 27 28 29 30这25个数,每个数出现的概率相等,去前面3*7个数,舍弃后面的4个数,将6 7 8转化成1, 9 10 11转化成2,……,公式:(a-3) / 3,其实跟解法1是一样的,包装一下而已。

    4、如何随机选取1000个关键字

    给定一个数据流,其中包含无穷尽的搜索关键字(比如,人们在谷歌搜索时不断输入的关键字)。如何才能从这个无穷尽的流中随机的选取1000个关键字?

    定义长度为1000的数组。对于数据流中的前1000个关键字,显然都要放到数组中。对于数据流中的的第n(n>1000)个关键字,我们知道这个关键字被随机选中的概率为 1000/n。所以我们以 1000/n 的概率用这个关键字去替换数组中的随机一个。这样就可以保证所有关键字都以 1000/n的概率被选中。对于后面的关键字都进行这样的处理,这样我们就可以保证数组中总是保存着1000个随机关键字。

    PS:关于1000/n的概率问题,可以这样来,随机生成1~n的数,如果是在1~1000内那么就替换相应位置

    5、在半径为1的圆中随机选取一点

    解法1:

    假设圆心在(0,0)。在x轴[-1, 1],y轴[-1, 1]的正方形内随机选取一点。然后判断此点是否在圆内(通过计算此点到圆心的距离)。如果在圆内,则此点即为所求;如果不在,则重新选取直到找到为止。正方形的面积为4,圆的面积为pi,所以正方形内的随机点在圆内的概率是 pi / 4。

    解法2:

    从[0, 2*pi)中随机选一个角度,对应于圆中的一条半径,然后在此半径上选一个点。但半径上的点不能均匀选取,选取的概率应该和距圆心的长度成正比,这样才能保证随机点在圆内是均匀分布的。

    关于不均匀选取问题:在一个直角三角形(斜边长pi,直角边长为1)斜边上随机选一点,然后投影到长为1的直角边应该满足条件。

  • 相关阅读:
    centos7没有ifconfig命令
    ubuntu建立快捷方式
    SIMTRACE环境搭建
    联想 Lenovo PWR-G60 无线掌中宝拆机
    初玩OpenWRT之编译TP-Link WR841N V8固件
    我有特殊的Windows激活姿势
    适用于WIFI Hacking的无线网卡推荐
    制作Wi-Fi Ducky远程HID攻击设备
    路由器固件安全分析技术(一)
    如何HACK无线家用警报器?
  • 原文地址:https://www.cnblogs.com/xkfz007/p/2760540.html
Copyright © 2020-2023  润新知