算法题
用返回0-6随机数的函数构造返回0-9随机数的函数
问题:给定一个返回06的随机自然数的函数,记为rand6(),则如何用rand6()函数构造返回09随机自然数的函数rand9()?
对于此问题,首先最容易想到的rand6() + 3、rand6() * 1.5、rand6()*2 - 4 等很明显都是错误的(rand6() + 3将返回的是3~9之间的随机数;rand6() * 1.5对于在返回int型的随机数时某些随机数不能产生;rand6()*2 - 4会产生负数)。
如果我们考虑相加,即rand6()+rand6(),这样产生的随机数的范围是012,所以我们在产生1012之间的随机数时不保留而重新产生一个新的随机数。这样做似乎是正确的,然而我们却忽略了一点:每个随机数产生的概率相等,即等概率。产生0只有一种可能(两次产生的随机数都为0),产生1却有两种可能(两次产生的随机数分别为0、1或1、0),……
再考虑相乘,即rand6()*rand6(),这样产生的随机数的范围是036,在产生3036之间的随机数时不保留而重新产生一个新的随机数,最后除以3返回。这样做同样不满足等概率,两次产生的随机数只要有一个为0返回的结果即为0,而只有两次产生的随机数都为1时返回的结果才为1,……
因此简单的直接相加相乘都是错的。
我们可以想,第一个rand6()产生一个06范围的随机数a,第二个rand6()产生一个06范围的随机数b,如果(a, b)这一对数可以确定一个唯一的数,从而可以产生的49个数(a和b都有7种可能,则(a, b)确定的数就有7*7=49种可能)都是等概率的。这就相当于有一个二维坐标系,X轴和Y轴上都可以取0~6的数,对于取x=a,y=b可以确定一个唯一的点(a, b)。
想到这个就是我们的关键,接下来就是想办法构造函数使得产生的每个数都是唯一的。我们不难想到把第一个rand6()产生的随机数a放在个位上,而把第二个rand6()产生的随机数b放在十位上,所有可能为:0006,1016,2026,3036,4046,5056,6066。很容易看出这些就是7进制数,所以我们用rand6()*7+rand6()就能生成049范围的随机数,而且每个数产生的概率相等,都是1/49。产生4049之间的随机数时不保留而重新产生一个新的随机数,这样产生039之间的数也是等概率的,仍然都是1/49,对于产生的数我们记为r(0<= r <=39),则个位数上的数就是想要的数。
具体的函数构造的C++代码如下:
int rand9() // 用rand6()编写随机产生0~9之间的数
{
int a, b, r;
do{
a = rand6(); // 随机产生0~6之间的数
b = rand6();
r = a * 7 + b;
} while (r >= 40);
return r / 10 +1;
}
如果进行测试,可写出完整代码如下:
#include <iostream>
#include <ctime>
using namespace std;
// 随机产生0~6之间的数
int rand6()
{
return rand() % 7;
}
// 用rand6()编写随机产生0~9之间的数
int rand9()
{
int a, b, r;
do{
a = rand6();
b = rand6();
cout << "a = " << a << ", b = " << b << endl;
r = a * 7 + b;
} while (r >= 40);
return r / 4;
}
int main()
{
srand((unsigned)time(NULL));
cout << "产生0~9范围的随机数为: " << rand9() << endl;
return 0;
}
原文链接:https://blog.youkuaiyun.com/wxbmelisky/article/details/50521435
一个01发生器,产生0(p)和1(1-p)不均匀,怎么设计使得它产生均匀的01序列
有一个随机数发生器,能以概率p生成0,以概率1-p生成1,问如何做一个随机数发生器
使得生成0和1的概率相等。
int gen()
{
int x,y;
while ( (x = rand()) == (y = rand()));
return x;
}
实数随机变量x和y分别在[0,a]和[0,b]之间均匀分布,再给一个实数z,问x+y < z的 概率
x和y分布式一个矩形,求直线x+y =z下边在矩形的面积和矩形本身的面积比
水塘抽样(Reservoir Sampling)问题
前言
水塘抽样是一系列的随机算法,其目的在于从包含n个项目的集合S中选取k个样本,其中n为一很大或未知的数量,尤其适用于不能把所有n个项目都存放到主内存的情况。
思路
在高德纳的计算机程序设计艺术中,有如下问题:可否在一未知大小的集合中,随机取出一元素?。
或者是Google面试题: I have a linked list of numbers of length N. N is very large and I don’t know in advance the exact value of N. How can I most efficiently write a function that will return k completely random numbers from the list
(中文简化的意思就是:在不知道文件总行数的情况下,如何从文件中随机的抽取一行?)。
两题的核心意思都是在总数不知道的情况下如何等概率地从中抽取一行?即是说如果最后发现文字档共有N行,则每一行被抽取的概率均为1/N?
我们可以:定义取出的行号为choice,第一次直接以第一行作为取出行 choice ,而后第二次以二分之一概率决定是否用第二行替换 choice ,第三次以三分之一的概率决定是否以第三行替换 choice ……,
以此类推。由上面的分析我们可以得出结论,在取第n个数据的时候,我们生成一个0到1的随机数p,如果p小于1/n,保留第n个数。大于1/n,继续保留前面的数。直到数据流结束,返回此数,算法结束。
问题一
首先考虑k为1的情况,即:给定一个长度很大或者长度未知数据流,限定对每个元素只能访问一次,写出一个随机选择算法,使得所有元素被选中的概率相等。
设当前读取的是第n个元素,采用归纳法分析如下:
n = 1 时,只有一个元素,直接返回即可,概率为1。
n = 2 时,需要等概率返回前两个元素,显然概率为1/2。可以生成一个0~1之间的随机数p,p < 0.5 时返回第一个,否则返回第二个。
n = 3
时,要求每个元素返回的概率为1/3。注意此时前两个元素留下来的概率均为1/2。做法是:生成一个0~1之间的随机数,若<1/3,则返回第三个,否则返回上一步留下的那个。元素1和2留下的概率均为:1/2* (1 - 1/3) = 1/3,即上一步留下的概率乘以这一步留下(即元素3不留下)的概率。
假设 n = m 时,前n个元素留下的概率均为:1/n = 1/m; 那么 n = m+1
时,生成0~1之间的随机数并判断是否<1/(m+1),若是则留下元素m+1,否则留下上一步留下的元素。这样一来,元素m+1留下的概率为1/(m+1),前m个元素留下来的概率均为:1/m * (1 - 1/(m+1)) = 1/(m+1),也就是1/n。
综上可知,算法成立。
问题二
将问题一中的条件变为,k为任意整数的情况,即要求最终返回的元素有k个,这就是水塘抽样(Reservoir Sampling)问题。要求是:取到第n个元素时,前n个元素被留下的几率相等,即k/n。
算法同上面思路类似,将1/n换乘k/n即可。
在取第n个数据的时候,我们生成一个0到1的随机数p,如果p小于k/n,替换池中任意一个为第n个数。大于k/n,继续保留前面的数。
直到数据流结束,返回此k个数。但是为了保证计算机计算分数额准确性,一般是生成一个0到n的随机数,跟k相比,道理是一样的。
同样采用归纳法来分析:
初始情况 n <= k:此时每个元素留下的概率均为1。
1、当 n = k+1 时,第k+1个元素留下的概率为k/(k+1),前k个元素留下的概率均为:k/k * (1 - k/(k+1) * 1/k) = k/(k+1),即上一步留下的概率乘以这一步留下的概率。
2、假设 n = m 时,每个元素留下的概率均为 k/n = k/m。
那么,当 n = m+1 时,第m+1个元素留下的概率为1/(m+1),前m个元素留下的概率均为:k/m * (1 - k/(m+1) * 1/k) = k/(m+1),其中:k/m为上一步留下来的概率,k/(m+1) * 1/k 为这一步不能留下来的概率(第m+1个留下来,同时池中一个元素被踢出的概率)。
综上可知,算法成立。
伪代码如下:
//stream代表数据流
//reservoir代表返回长度为k的池塘
//从stream中取前k个放入reservoir;
for ( int i = 1; i < k; i++)
reservoir[i] = stream[i];
for (i = k; stream != null; i++) {
p = random(0, i);
if (p < k) reservoir[p] = stream[i];
return reservoir;
原文链接:https://blog.youkuaiyun.com/qq_25026989/article/details/89284058
随机排列函数shuffle
原理
for(int i = 0; i < n; ++i)
{
a[i] = i;
}
for (int i = 0 ; i < n ; ++i)
{
swap(a[i],a[rand()%(n-i) + i]);
}
使用
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
vector<string> str;
str.push_back("hello");
str.push_back("world");
str.push_back("welcome");
str.push_back("to");
str.push_back("Beijing");
std::random_shuffle(str.begin(),str.end());//迭代器
for(int j = 0; j < str.size(); j++)
{
cout<<str[j].c_str()<<" ";
}
cout<<endl;
system("pause");
return 0;
}
带权采样问题
1.按照权值进行采样,当总数很大时不适用
2.每个元素按照权值对应一个区间,产生一个总空间的随机数,再用二分查找最后对应的元素
3.先按正常的概率随机选一个,再根据权值能否要这个数,能则选取,不能则不要了