伪随机数生成

5 阅读2分钟

普通随机数

// 生成[min,max)之间的随机数
function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
}

加权随机数

有时需要随机选取道具,但是只使用普通的Math.random不能完成需求,例如每个道具有不同的权重:

  • apple: 50
  • banana: 20
  • orange: 30
  • grape: 30

此时最适合使用加权随机数(总权重不需要保证为100):

function randIndexByWeights(weights) {
    let totalWeight = 0;
    const prefixSums = weights.map(weight => totalWeight += weight);
    const randomNum = Math.random() * totalWeight;
    
    for (let i = 0; i < prefixSums.length; i++) {
        if (randomNum < prefixSums[i]) {
            return i;
        }
    }
}
​
randIndexByWeights([50,20,30,30);

线性同余生成器

虽然编程语言都提供了开箱即用的随机数生成器,例如C语言的rand,js语言的Math.random(),但有时就是需要定制一个更适合业务逻辑的随机数生成器,所以掌握一个可控的伪随机数生成算法是很有必要的,其中最简单的就是线性同余随机数。

公式:Xn+1=(aXn+c)%mX_{n+1} = (aX_n + c) \% m

其中:

  • m 是一个大于 0 的整数,称为模数。
  • a 是一个大于 0 且小于 m 的整数,称为乘数。
  • c 是一个大于等于 0 且小于 m 的整数,称为增量。
  • X0X_0是初始种子值,也是第一个生成的随机数。

C语言的rand用的就是这种算法,一般会选取:

m = 32767
a = 1103515245
c = 12345

示例代码

class Random {
    constructor(seed) {
        this.seed = seed;
        this.xn = seed;
        this.m = 32767;
        this.a = 1103515245;
        this.c = 12345;
    }
    next() {
        const x = (this.a * this.xn + this.c) % this.m;
        this.xn = x;
        return x;
    }
}
​
function main() {
    const r = new Random(0);
    for (let i = 0; i < 100; ++i) {
        console.log(r.next());
    }
}

Fisher-Yates洗牌算法

如果需要打乱一个数组,可以用洗牌算法,其中最经典的就是Fisher-Yates算法:

function fisherYatesShuffle(arr) {
    for (let i = arr.length - 1; i > 0; i--) {
        // 生成从0到i的随机数,包括0和i
        let j = Math.floor(Math.random() * (i + 1));
        // 交换arr[i]和arr[j]
        [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr;
}