随机化算法(randomized algorithm)

随机化算法(randomized algorithm)的运行时间不只依赖于特定的输入,而且依赖于所发生的随机数

对于快排而言,使用随机枢纽元(pivot),将获得 O(NlogN) 的期望时间。

1. 随机数生成器(generator)

产生随机数的最简单方法是线性同余数发生器,它于 1951 年由 Lehmer 首先提出。随机数列 xi 的生成满足:

xi+1=AximodM

为了开始这个序列,必须给出 x0 某个值,这个值就做种子(seed)。如果 x0=0,那么这个序列永远不是随机的,但是如果 A 和 M 选择得正确,对于任何地 1x<M 都是同等有效的。如果 M 是素数,那么 xi 就绝不会是 0.,比如作如下取值,A=7,M=11,x0=1,那么所产生的随机数为:

  • 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=2311=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 = 2311 时,Q = 44 488,R = 3399,R < Q,

则有:

xi+1===AximodM=AxiMAxiMAxiMxiQ+MxiQMAxiMAxiMxiQ+M(xiQAxiQ)

等等经历一系列等价替换之后,最终得:

xi+1=A(ximodQ)RxiQ+Mδ(xi)

其中 δ(xi) 的值非 0 即 1。仅当余项的值(A(ximodQ)RxiQ)小于 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;
}
posted on 2016-10-12 12:55  未雨愁眸  阅读(371)  评论(0编辑  收藏  举报