從擁擠的兔子到僞隨機數算法

寫於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. 更快的性能。
相關文章
相關標籤/搜索