1. 随机数
随机数,也即“随机选择的数”,是在一个有限数集上的一个一致分布的随机序列。随机数在许多方面有应用,如仿真、抽样、数值分析、计算机程序、娱乐等方面都有所应用。计算机用确定算法计算出来的随机数系列是伪随机数,并不是真正的随机数,但是其具有随机数的统计特征,如均匀性、独立性等。
在上世纪初,冯诺伊曼建议用平方,然后取结果中间的数据作为随机数(平方取中法)。结果看起来修似乎是随机的,但是很多人对此质疑。实际上这并不是好的随机方法,尤其是针对一些短循环序列。
2. 线性同余序列
2.1. 概念
后来,有人发明了线性同余方程,用此方程生成的一组序列,随机效果不错。线性同余方程生成的序列,被称为线性同余序列。线性同余序列在一个周期内重复出现。并且方程中有四个魔数(常数),但是这四个魔数,对随机的效果影响巨大。线性同余发生器(Linear congruential generator,LCG)用于生成随机序列。
LCG定义:
- X是随机数序列
- m,0<m,模
- a,0<a<m,乘子
- c,0≤c<m,增量,也叫做偏移量
- X0 , 0≤X0<m,开始值,通常叫做“种子”seed
当c=0时候,LCG就变换成了乘法同余发生器,multiplicative congruential generator (MCG), c≠0时叫做混合同余发生器,mixed congruential generator,MCG。
2.2. 示例
上面的线性同余方程,选定a、X、c和m四个魔数,可以生成线性同余序列,线性同余序列在一个周期内存是随机的,独立的,一个周期完成之后,循环进行。
当m=9,a=2,c=0,seed=1时,得:
数据示图如下:
如上图所示,m、a、c取值不同,其循环的周期长度是不同的,并且随机的效果也是不同的。
2.3. 最大周期长度
m、a、c取值不同,其循环的周期长度是不同的。为了使得线性同余序列的周期长度达到最大m,需要满足以下3个条件:
- m and c are relatively prime(互为质数,即两个数没有除1之外的公约数)
- a-1 is divisible by all prime factors of m(m的质因数(可以整除m的所有质数),都能整除a-1,也即a-1是m的所有质因数的倍数)
- a-1 is divisible by 4 if m is divisible by 4 (如果m被4整除,a-1也被4整除,即m是4的倍数,a-1也要是4的倍数)
详细证明过程,感觉兴趣的可以看《计算机程序设计艺术》第二卷的随机数章节,有详细证明过程的介绍。
- 是质数
/*判断是否为质数*/
bool prime(int N)
{
if (N < 2)
return false;
if (N == 2)
return true;
for (int i = 2; i < std::sqrt(N*1.0); i++)
{
if (N%i == 0) //i能被j整除,N不是质数
{
return false;
}
}
return true;
}
- 互质
bool isrp(int a, int b)
{
if(a <=0 || b<=0 || a==b)
{ // 互质整数不能小于或等于0
return false;
}
else if(a==1 || b==1)
{ // 两个正整数中,只有其中一个数值为1,两个正整数为互质数
return true;
}
else
{
// 求出两个正整数的最大公约数
while(1)
{
int t = a%b;
if(t == 0)
{
break;
}
else
{
a = b;
b = t;
}
}
if( b > 1){ // 如果最大公约数大于1,表示两个正整数不互质
return false;
}else{ // 如果最大公约数等于1,表示两个正整数互质
return true;
}
}
}
- 所有质因数
class QualityFactor
{
public:
vector<int> m_vInt;
public:
void QFContract(long a) //用短除法对合数进行分解
{
while(a>1)
{
for(int i=2;i<=a;i++)
{
if(a%i==0) //短除法
{
a = a/i;
if (std::find(m_vInt.begin(), m_vInt.end(), i) == m_vInt.end())
m_vInt.push_back(i);
break;
}
}
}
}
};
- 所有质因数整除
int MaxCommondMultiple(const vector<int>& _vInt, BOOL _bFour, int _nSrc)
{
int nCommonMultiple = 1;
while (nCommonMultiple < _nSrc)
{
for (auto it = _vInt.begin(); it != _vInt.end(); ++it)
{
nCommonMultiple*= *it;
if (_bFour && (nCommonMultiple%4 != 0))
{
continue;
}
BOOL bFind = TRUE;
for (auto it = _vInt.begin(); it != _vInt.end(); ++it)
{
if (nCommonMultiple % *it != 0)
{
bFind = FALSE;
break;
}
}
if (bFind)
{
return nCommonMultiple;
}
}
}
return 0;
}
- 随机数函数
static int seed = 0;
int random(int a, int b, int m)
{
seed = (seed * a + b) % m;
return seed;
}
输入最大周期长度m,即可以根据上述函数生成相应的a和c。
2.4. 其他
2.4.1. 标准库rand
m、a、c、X是一级相关数,选定m之后,同样会有很多组对应的a、c、X。不同的数据,对应的随机数分布是不一样的。随机效果也是大大不同。理论上m选择的范围越方,其效果越好。而VS实现的std::rand()即是采用线性同余方程实现的。
*/
int __cdecl rand (
void
)
{
_ptiddata ptd = _getptd();
return( ((ptd->_holdrand = ptd->_holdrand * 214013L
+ 2531011L) >> 16) & 0x7fff );
}
其周期较小,所以随机效果不是特别好。另外其中的几个常数,是经常大量实验选择的,是随机效果相对较好的一组数。
2.4.2. 自定义rand
什么时候需要自定义rand()呢?例如我要取0-10之间的随机数,并且每次不能相同,且可以取完一个周期。一般情况下我们会使用“洗牌”算法,即在数据序列中,依次取出一个数与后面的随机取的数交换,即可以达到效果。标准库提供了std::shuffle实现此功能。
std::vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::shuffle (v.begin (), v.end (), std::default_random_engine (0));
大多数时候,我们使用上面的方法,可以得到效果非常好的随机序列。但是如果我需要一个0-0xFFFFFFFF之间的随机序列呢?此时就不方便使用构造一个巨大的数据序列之后,再调用洗牌函数打乱数据。因为数据序列的内存消耗巨大。
此时,就需要使用一个自定义周期长度为0xFFFFFFF的rand()函数了。即,我们选定m=0xFFFFFF,根据三个条件调用相应的函数求出相应的m、c、X。但是因为参数的可能性太多,随机性差异巨大,需要经过大量实验,才能选择一组较好的数据组合供使用。
示例代码
2.4.3. 最佳随机数
线性同余发生器一直是随机数生成的主要方式,但是其随机效果不是特别好。C++11引入了梅森旋转演算法的随机数引擎,是目前效果最好的随机算法。std::default_random_engine默认即使用梅森旋转演算法。也可以直接使用梅森旋转演算法引擎std::mt19937。