乱序的数学:随机数生成公式的民间讲法 别光看那些高大上的标题,咱接着往下看。生成随机数这事儿,在数学圈子里叫"entropy",翻译成大白话就是“熵”。想象一下,你手里有个箱子,里面装着球的编号,你想从中摸出一张牌,概率通不过脑子的话,你根本不知道会不会摸到 9 号球。
这时候,你就需求个工具把这个箱子里的数字搅和乱,把每个数字出现的几率拉平。 别被那些复杂的算法吓到,一般/平平使用者实际上主要靠三个东西:rand()、rand() 加种子、还有真正的随机数生成器。
说白了,就是让数字在数字世界里乱跑。大量人认定 rand() 靠谱,但你知道它有个致命弱点吗?那就是“可达性”。
比如你要生成从 1 到 100 的随机整数,rand() 可能会在你需求时恰好生成 101,要么在你没要的时候生成 999。
这时候你就得用种子,用种子去重解,要么用种子去重分次。
这就像你做饭,电饭煲没电了要么坏了,你就得用燃气灶在旁边伺候,不然这顿饭做不好。 至于真正的随机数生成器,那更是靠得住。它不靠序列号,也不依赖种子,它靠物理世界。
比如你拿手机里的随机数生成器,底层一般是结合工夫戳、鼠标移动速度、键盘敲击顺序,就连环境噪点,这些乱七八糟的东西混在一起,最终通过几层加密算法变成一串数字。它不是为了“看起来”随机,而是为了确实随机。
这种算法一般没有“种”,要么说种是物理世界赋予的。 你看,生成随机数这事儿,核心逻辑实际上就两点:一个是概率分布的难题,另一个是数字生成源的难题。 起初,如何把数字往平均值上拉?这是最经典的难题。
要是直接要 1 到 100 之间的数,rand(100) 可能给你 1,也可能给你 99,就连 10000。
这时候你就得用线性同余法(LCG)。
这种算法有个公式:x_{n+1} = (a x_n + c) mod m。
这里的 x_n 是当前的随机数,a、c 是常数,m 是模数。
关键在于模数 m 得是庞大的质数要么大整数,并且 a 和 c 得精心挑选,确保范围被彻底覆盖。
比如要 1 到 30,你能够用 Mersenne 数(Mersenne prime)来构造模数,这样生成的数字分布会比较均匀,不会有啥长尾巴。 如何保证这些数字是均匀的?大量时候我们当作 rand() 随意给的,实际上未必均匀。
比如 1 号球可能握起来好办,99 号球握起来艰难,这时候你就需求引入扰动。扰动就是把数字加一个随机的偏移量,要么乘以一个大素数再取模。就像你在洒桌上撒盐,热气会让盐粒分散,要是直接挖一口深井,那里的盐可能特别少。
这两个思路合起来,就形成了所谓的“均匀分布算法”,比如线性反馈移位寄存器(LFSR)。 再看种子和重解的难题。大量人当作种子就是给程序起个名字,比如“生成 100 个数”,那种子就是 101 要么 999。但这实际上是个误会。种子在真正的随机数生成器里是独立的物理量,它本身不拍板输出,它只是拍板了输出的起始状态。一旦物理状态变了,输出必然不同。
要是两次运行生成器,种子不同(要么环境变了),输出自然不同。
这就好比两颗种子种在两个不同的花盆里,长出来的叶子形状可能不同,但它们都是同一颗种子,只是环境不同。 另外,重解也是个绕不开的坎。
要是你用 rand(1000) 生成 1 到 100 的数,万一它生成了 1000,你得把它截断,变成 1,再拿结局去重解。
这时候要是重解算法效率低,程序就会卡死,就连超时。
这时候就得换一种策略,比如分段生成,要么用哈希函数把长串数字压缩成短串,削减去重难度。 还有,随机数生成器的“种”是啥?大量开发者会搞错。有些工具生成的随机数是伪随机的,它们依赖种子,故此种子不同输出不同,但本质上是确定的,能够预测。真正的随机数生成器才是不依赖种子的,它是看天进食,看环境、看工夫、看物理现象。
要是一定要用伪随机数,那种子就是物理状态,比如工夫戳,要么鼠标坐标。 最终,关于性能,这也是个老难题。rand() 在 CPU 上本来就快,但真正的随机数生成器开销大。
要是你需求生成 10 亿次随机数,用伪随机的好好说,用真正的可能就卡得半死。
这时候的权衡就挺现实了:要速度就要牺牲均匀度,要均匀度就得接纳工夫成本。 总的来说,随机数生成算法是个不断在概率、均匀性、性能之间找平衡的过程。
不是有个万能公式,而是得根据具体场景,看看你要的是“快”还是要“准”,是“真”还是要“准”,然后你自己去调参、去优化。
这才是咱真正理解数学的原理,而不是死记硬背公式。