这篇博客总结下sumnous 大神 博客的随机算法,每次大神英文名都会拼错= =
http://sumnous.github.io/blog/2014/05/13/random-pick-function/
如果给定了一个要在某些概率区间返回某数,例如 0.6概率生成2,0.3.概率生成1, 0.1 概率生成0,怎么实现。简单而言就是 输入[[0.6,2],[0.3,1],[0.1,0]]
其实一个简单做法就是用个均匀分布,举例,在前0.6的返回2,中间0.3的返回1,最后0.1的返回0。其实随便哪个0。6的部分都可以,甚至可以不连续,按照这个思路,有大神的python代码,大神是python流。
def withProbRandomPick(probList):
import random
import sys
r, s = random.random(), 0
for num in probList:
s += num[1]
if s >= r:
return num[0]
print >> sys.stderr, "Error: shouldn't get here"
简单而言就是概率一直累加,累积到大于生成的随机数,就返回对应的返回值。感觉这个不够make sense,先写个最通俗版的
int f()
{
while(1)
{
int x=rand()%10;
if(0<=x<5)
return 2;
if(5<=x<8)
return 1;
if(8<=x<=9)
return 0;
}
}
这样可能看起来不够牛叉,但是比较接地气,一下子就看懂。但是这种写法不够通用,因为你不知道输入有多少个概率区间,于是乎就整合成上面的版本了。
这道是入门级的,现在看一道稍微challenge的题,利用一个随机函数(0.6概率返回1,0.4概率返回0),生成0.5概率返回1,0.5概率返回0的随机函数,当然不能用系统库
这道一眼反应就是凑一个概率等于0.5,由于是两点分布,我想到用二项分布,然后将多个基本事件加起来等于0.5,也即凑一个概率和等于0.5,但是发现试验次数N=2,3,4,5一直下去,小数点越来越多,几乎不太像能凑到0.5,难道无限多有个极限是0.5?我也没想那么多,sumous大神给出的答案让我目瞪口呆,直接两项把00 11的结果丢弃。。。我思维太狭窄了,不够开阔,这样显然 01 10的概率都是0.24了,于是乎代码如下:
def g():
while(true):
a = f()
b = f()
if [a,b] == [0,1]:
return 0
if [a,b] == [1,0]:
return 1
另外再附上一道最近一直想的题目,由这道题目可以归纳出一个稍微general的规则出来。由rand5() 生成rand7(),randi()是生成i个数的随机函数.这道题目在Lucida大神称为翔一样书的程序员面试宝典里面也作为例子,思路就是 x=5*rand5()+rand5() 这样可以产生一个更大的数,然后模7,就可以生成均匀概率的7个数了。随机函数一定是生成输出空间里每个数的概率都一样的,不然怎么称为随机,尽管计算机都是伪随机数。简单说明就是5*[0,4]+[0,4]=[0,24]
其实也就是生成了5^2个点,然而这里每个点都只有一个 两次生成的数字对应,并且连续的从0,24,不会漏数,这样有个极大的好处:第一这样保证每个点的概率都一直,1/25,举个例子,如果用4*rand5()+rand5()的话,生成4就有[1,0] [0,4]两种实验结果,不确保概率均一性 第二中间不会遗漏数,这样到后面可以方便取模,最后模拟之后根据7的值丢弃掉一些多出的部分,x%21 x%14 x%7应该都是可以的,只是%21会使丢弃的最少,这样可以更快的产生需要的数字,%14可能需要多次的循环。所以总结就是randi
就用x=i*randi()+randi(), 这样将结果空间等概率的投射到i^2的连续空间,只要7<i^2, 原则上都可以这么做,附上大神代码:
def rand7():
while(true):
num = 5 * rand5() + rand5()
if num < 21:
return num % 7
最后一道思考题,如果rand7()->rand5(),感觉是一种把戏,
def rand5():
while(true):
num = 7 * rand7() + rand7()
if num < 45:
return num % 5
包括July大神博客总结的rand7()->rand10(),应该也是一样的技巧。
经过大量随机实验测试,结果比较接近理论分析值
#include <stdio.h>
#include <stdlib.h>
#include <string>
int rand5()
{
return rand()%5;
}
int rand7()
{
while(1)
{
int x=5*rand5()+rand5();
if(x<21)
return x%7;
}
}
int main()
{
int Percent1Count=0;
const int RandomNum=7;
int count[RandomNum];
memset(count,0,sizeof(int)*RandomNum);
for(int i=1;i<=10000000;i++)
{
int x=rand7();
count[x]++;
}
for(int i=0;i<RandomNum;i++)
{
printf("%d: %d\n",i,count[i]);
}
return 0;
}
另外最近看到July大神博客
http://blog.youkuaiyun.com/v_july_v/article/details/6419466
总结的求数组几个数和为给定值,二分方法的时间复杂度为O(nlogn) 我就有点怀疑了,因为时间复杂度是上界的最小值,是否不够渐进,因为操作次数是log(n-1)+...log1, 想到mathmatics for computer science(MIT和Google联合出的,之前微博看到,里面递归的部分特别喜欢) 一书给出的单调不减函数求和式上下界的一个不等式,遂用,发现上界还是O(nlogn), 我猜July大神应该是直觉上估计的,因为n次loop,每次logn 但是这个n会逐渐变到1,所以我有点怀疑,不过结论还是打消了疑虑