随机化算法(randomized algorithm)
随机化算法(randomized algorithm)的运行时间不只依赖于特定的输入,而且依赖于所发生的随机数。
对于快排而言,使用随机枢纽元(pivot),将获得 O(NlogN) 的期望时间。
1. 随机数生成器(generator)
产生随机数的最简单方法是线性同余数发生器,它于 1951 年由 Lehmer 首先提出。随机数列 xi 的生成满足:
为了开始这个序列,必须给出
x0
某个值,这个值就做种子(seed)。如果
x0=0
,那么这个序列永远不是随机的,但是如果 A 和 M 选择得正确,对于任何地
1≤x<M
都是同等有效的。如果
M
是素数,那么
- 7,5,2,3,10,4,6,9,8,1,7,5,2…(循环出现)
# n 控制随机数的个数
def randgen(n):
A, M, x = 7, 11, 1
for _ in range(n):
x = x*A % M
yield x
如果 M 选择得很大,比如 31 比特(7FFF FFFF)的素数,那么对于大部分的应用来说,周期是非常长的,Lehmer 建议使用 31 个比特的素数 M=231−1=2147483647 。对于这个素数, A=48271 是给出整周期发生器的许多值中的一个。
C 程序员来说,在实现时,全局变量用来存放随机数序列的当前值(本文件全局可见)。这个全局变量交由某个程序初始化。当在调试一个使用随机数的程序的时候,最好设置种子(seed,也即 x0 )为 1,这使得总是出现相同的随机序列(伪随机序列)。当程序工作时,可以使用系统时钟(时刻都在变化),也可以要求用户输入一个值作为种子。
static unsigned long Seed = 1;
#define A 48271
#define M 2147483647
double Random() {
Seed = (Seed * A) % M;
return (double) Seed / M;
// (0, 1)
}
void Init(unsigned long InitVal) {
Seed = InitVal;
}
对程序中的加法和乘法一定要特别注意,因为这些运算可能会产产生溢出,虽然不会提示错误,但会影响最终的结果,从而影响伪随机性。
Schrage 给出了一个即使在 32 位机器上执行也不会发生溢出的方案,计算 M/A 的商和余数分别为 Q (quotient)和 R(Remainder)。在 A = 48271,M = 231−1 时,Q = 44 488,R = 3399,R < Q,
则有:
等等经历一系列等价替换之后,最终得:
其中 δ(xi) 的值非 0 即 1。仅当余项的值( A⋅(ximodQ)−R⌊xiQ⌋ )小于 0 时, δ(xi)=1 ,
static unsigned long Seed = 1;
#define A 48271
#define M 2147483647
#define Q ( M / A )
#define R ( M % R )
double Random() {
Seed = A*(Seed % M) - R * (Seed / Q);
if (Seed < 0)
Seed += M;
return (double) Seed / M;
}
void Init(unsigned long InitVal) {
Seed = InitVal;
}