从拥挤的兔子到伪随机数算法

写于2017年7月30日。 阅读《复杂》一书时的笔记及延伸。 本文涉及到的生态学、数学、计算机科学部分都比较肤浅,若有错漏,欢迎斧正。javascript

原文php

又是兔子

咱们多少接触过这样一个问题java

  1. 第一个月初有一对刚诞生的兔子
  2. 第二个月以后(第三个月初)它们能够生育
  3. 每个月每对可生育的兔子会诞生下一对新兔子
  4. 兔子永不死去

问:第n月时,一共有多少只兔子?算法

对,描述兔子数量的数列就是咱们熟悉的斐波那契数列,不少人都是经过这个问题了解递归的吧(也许吧,也多是Tower of Hanoi)?dom

不过兔子怎么可能“永不死去”呢!那咱们如今开始考虑兔子的出生率和死亡率。先从容易的来:每对兔子父母每一年生四只小兔,而后死去。若是一开始有两只兔子(第0年),那么第1年会有四只兔子,第二年会有8只兔子……每一年兔子的数量会翻一番。记第 t 年的兔子数量为 n_t ,那么: n_t=2 \cdot n_{t-1} ,即 n_t = 2^t\cdot n_0 。很显然,若是不受限制,兔子的数量会愈来愈多,最终撑满这个星球,乃至整个宇宙。性能

假如咱们考虑种群数量增加所受的限制呢?可想而知,若是种群数量愈来愈多,也许不少小兔子因为太过拥挤,缺乏食物和空间,没有繁殖就死去。整个种群的数量就不会呈现上述无限增加的状况了。生物学家用一种叫 logistic model 的简化方式来描述这种状况下的群体增加: n_{t+1} = (r_{born}-r_{die})\cdot(k\cdot n_t-n_t^2)/k。 其中,n_t 为这一代的种群数量,r_{born} 为出生率,r_{die} 为死亡率,k 为承载能力。spa

举个例子,若是出生率为2,死亡率为0.4,承载力为32,第1代有20只兔子,套用上面的模型,那么第2代有12只兔子;再把这个结果代入计算,会得出第三代仍然有12只兔子;今后种群数量维持在12。code

若是咱们改变一些参数,好比把死亡率降到0.1,那么依据模型计算,第2代有14.25只兔子,第三代为15.01816只兔子(不要在乎兔子的数量为何能够是小数)。实际上咱们的计算过程是把这一代的计算结果代入模型公式,计算下一代的结果,这个重复不断的过程就是迭代,对模型进行迭代。cdn

logistic map

由 logistic model 进行迭代计算仍是有些麻烦,咱们能够再进行一点简化:把出生率和死亡率合成一个变量 R,种群数量由承载率(当前种群数量与最大可能的种群数量的比率)x 代替,x = n / k,因为当前种群数量老是介于0和 k 之间,因此 x 老是介于0和1之间。blog

那么咱们就能够把上面的logistic model的公式改写一下,因而咱们有: x_{t+1}=R \cdot x_t \cdot (1-x_t)。这个方程称为logistic map,是动力系统理论和混沌研究中的一个重要方程。

若是咱们让 R 值变化,那么logistic map就颇有趣了。

不妨先假定 R=2,咱们发现,无论 x 的初始值是什么(先用0.2试试吧),最后总会达到同一个固定的值:

\begin{gathered}
x_0=0.2 \\ x_1=0.32 \\ x_2=4352 \\ x_3=0.4916019 \\ x_4=0.4998589 \\ x_5=0.5 \\ x_6=0.5 \\ ...
\end{gathered}

不一样的初始值只会让到达0.5的速度有快有慢,但通过若干步骤后,都会到达0.5。那么这个0.5就称为不动点(fixed point)。

咱们把 R 的值调大,也许这样的不动点依而后存在,但到达不动点的速度会愈来愈慢。假如 R=3.1 ,咱们再来看看,会发现状况彷佛不太同样了:

R=3.1,x0=0.2

R=3.1,x0=0.2

x 的值最终会在0.5580141和0.7645665之间振荡。咱们称之为吸引子。

随着 R 值的变化,情形会更加复杂。我从wikipedia上把结论摘录以下:

  1. 0和1之间:不论初始值数值为什么,x 会愈来愈少,最后趋近于0。
  2. 1和2之间:不论初始值数值为什么,x 会快速的趋近 (R-1)/R
  3. 2和3之间:通过几回迭代,x 也会愈来愈接近 (R-1)/R,但一开始会在这个值左右振动,而收敛速率是线性的。
  4. 3:x 仍然会愈来愈接近 (R-1)/R,但收敛速率极为缓慢,不是线性的。
  5. 3和1+\sqrt{6}(约3.45)之间:针对几乎全部的初始值,x 最后会在2个值之间持续的震荡,即 x 最后会是a,b,a,b...的变化,其数值和 R 有关。
  6. 3.45和大约3.54之间,针对几乎全部的初始值,x 最后会在4个值之间持续的震荡。
  7. 约大于3.54:x 最后会在8个、16个、32个值……之间持续的震荡,至于 R 什么时候会令 x 的值由 n 个到 2n 个,则和费根鲍姆常数 \delta = 4.669... 有关。
  8. 约为3.5699:这样的振动消失,整个系统开始在混沌的状态之中。针对几乎全部的初始值,都不会出现固定周期的震荡,初值再微小的变化,随着时间都会使结果产生明显的差别,这就是典型混沌的特性。
  9. 大于3.5699:整个系统在混沌的状态之中。不过,当中有些特定的 R 值仍是使系统变成非混沌,有周期性的结果,这些区间称为“稳定岛”。例如当 R 约大于3.82,会出现3个值的周期,再大一点出现6个值及12个值的周期。
  10. R 从大约3.5699到大约3.8284之间,系统混沌性质发展的现象有时会称为Pomeau–Manneville场景,其特征是周期性的震荡和非周期性的行为会穿插出现。此特征能够应用在半导体元件中。也有其余区域会使系统的周期为5个值,无论任意周期都存在某特定的 R,使周期为指定值。
  11. 大于4:针对几乎全部的初始值,系统最后都会超过区间[0,1]而且发散。

彷佛有点枯燥,不过不要紧,咱们来画个图吧,经过图形来了解一下这个混沌系统。取一个超过3.5699, 不超过4的 R 值,这里咱们不妨取 R = 4,仍是令 x 初始值是0.2,那么迭代100次:

R=4,x0=0.2

R=4,x0=0.2

一个感受,杂乱无章。对于每一个 x 的值,虽然它能惟一决定下一个 x 的值是什么,但它们却组合成一个看起来很是随机的轨迹。在计算机中,咱们甚至能够用它来生成伪随机数。所以,表面的随机性可能来自很是简单的肯定性系统。

不只如此,对于一个能产生混沌的 R 值,若是初始值有任何的不肯定性,那么必定时间后的轨迹就没法预测了。仍是刚刚的图,咱们考虑稍稍修改一下初始值,好比修改小数点后第10位,假定初始值是0.2000000001会怎么样?这与0.2很是接近,咱们把两条轨迹绘制到一块儿看看:

能够看出,大概在 t = 30时,两条轨迹已经分开,x 的值已经没法预测了。实际上只要初始值有不肯定性,无论精确到小数点后多少位,最终都会在 t 大于某个值的时候变得没法预测。

伪随机算法

接下来,咱们就来尝试使用logistic model的混沌特性来实现一种简单的伪随机算法。

function* RandomGenerator() {
  let seed = .2;
  function getNext(x) {
    return 4 * x * (1 - x);
  }
  for (let i = 0; i < 30; i++) {
    seed = getNext(seed);
  }
  while (true) {
    seed = getNext(seed);
    yield seed;
  }
}

const randomGenerator = RandomGenerator();

function random () {
  return randomGenerator.next().value;
}
复制代码

实际上一些常见的伪随机数生成算法,例如线性同余法,也是使用了相似的手段,经过迭代产生混沌系统。而这类的算法所追求的,我以为有三个方面的内容:

  1. 统计意义上的随机,即随机数结果分页均匀,不能有所倾向。
  2. 更大的循环区间。好比像如上算法,受 R 值和计算机浮点数精度的影响,必定会在某个时刻产生与以前相同的结果,使得迭代进入循环。而用线性同余算法,循环区间则与选取的模数大小相关。
  3. 更快的性能。
相关文章
相关标签/搜索