【小當心得】證實洗牌算法隨機性

本文對解釋洗牌算法比較簡略,主要在於證實其隨機性算法

本文是學習洗牌算法的心得,表達不當或理解不對的地方,感謝您點評指正~數組

打亂一個數組

打亂一個沒有重複元素的數組。瀏覽器

好比 const arr = [1, 2, 3, 4, 5], shuffle(arr) 後返回一個隨機的數組。markdown

隨機的意思是數組上的每一個數字出如今每個位置的可能性都是隨機的dom

Array.prototype.sort 作獲得嗎?代碼以下oop

function shuffle(arr) {
   return arr.slice().sort(function(){ return Math.random() >= 0.5 ? -1 : 1;}); 
}
複製代碼

因爲瀏覽器內核各自實現不一樣 Array.protype.sort 可能使用快排、歸併,甚至冒泡等,因此沒法保證其隨機性post

洗牌算法

上面咱們解釋了隨機的意思,數組上的每一個數字出如今每個位置的可能性都是隨機的。有一種比較經常使用的算法,洗牌算法。(參閱 維基百科-洗牌算法)下面咱們使用 JavaScript 實現它學習

function shuffle() {
    const arr = this.nums.slice();
    let choice = arr.length; // 這個 choice 有兩層含義
    while (choice > 0) {     // choice > 0 表示能夠選擇的個數大於 0
        let j = Math.floor(Math.random() * choice);  // 第一層含義:表示在多少個數(選擇)裏隨機選一個,獲取隨機索引 [0, choice)
        choice--;   // choice-- 獲得的是另外一個含義,也就是索引、所在位置;因爲數組是以 0 做爲下標,須要往左偏移,
        [arr[j], arr[choice]] = [arr[choice], arr[j]];  // 交換這兩個數,完成了索引爲 choice 的 arr[choice] 的隨機
    }
    return arr;
}
複製代碼

根據代碼分析,while (choice > 0) {} 時間複雜度 O(n), 交換位置時間複雜度 O(1), 因此它的時間複雜度 T(n) = O(n); this.nums.slice(); 得出它的空間複雜是 O(n)ui

推導它超出我了個人認知範圍,若是有小夥伴懂的,能夠在留言區告訴我哈~this

證實洗牌算法的隨機性

咱們要證實的是

假設總共有 n 個數,其中數 a 出如今第 i 個位置的可能性,爲 1/n。

這裏的 i 是任意天然數、n 是任意正整數、a 是取自 n 的一個數。

根據上面的假設,假設是從後往前考慮,由於上面洗牌算法代碼也是從後往前的

能夠獲得,數 a 不會出如今第 n-1 個位置,也不會出如今第 n-2 個位置,... 也不會出如今第 i+1 個位置,在 第 i 個位置出現了,數 a 只有一個,後面的狀況,已經與 a 無關了。

上面提到的幾種狀況,第 n-1 個位置到第 i 個位置,它們對應着什麼,關係又是什麼?

咱們一步步地看

  1. 數 a 不會出如今第 n-1 個位置,也就是從未被挑選的數(有 n 個)裏,挑選不等於 a 的數(有 n-1 個),放在第 n-1 個位置,可能性 (n-1)/n,假設 b 被挑選出

    對應的是代碼第一次循環

    let choice = n; // 表示在 n 個數(選擇)裏隨機選一個
     while (choice > 0) {     // choice > 0 表示能夠選擇的個數大於 0
         let j = Math.floor(Math.random() * choice);  // 索引是隨機的,arr[j] 恰好不等於 數 a ,可能性是 (n-1)/n , 換一種說法就是 數 a 不在這個 choose-- 這個位置上
         choice--;   // 因爲數組是以 0 做爲下標,須要往左偏移
         [arr[j], arr[choice]] = [arr[choice], arr[j]];
     }
    複製代碼
  2. 數 a 也不會出如今第 n-2 個位置,同樣的從未被挑選的數裏,挑選不等於 a 的數,放在第 n-2 個位置,這裏創建在第一步成立的條件下,由於 b 已經被挑選出了,未被挑選的數個數是 n-1 個, 不等於 a和b 的數(有 n-2 個),因此這一步的可能性是 (n-1)/n * (n-2)/(n-1)

    對應的是代碼第二次循環

    while (choice > 0) {     // choice > 0 表示能夠選擇的個數大於 0, 這裏 choice = n-1
         let j = Math.floor(Math.random() * choice);  // 索引是隨機的,arr[j] 恰好不等於 數 a ,可能性是 (n-2)/(n-1) , 換一種說法就是 數 a 不在這個 choose-- 這個位置上
         choice--;   // 因爲數組是以 0 做爲下標,須要往左偏移
         [arr[j], arr[choice]] = [arr[choice], arr[j]];
     }
    複製代碼

...(中間過程用...表示)

  1. 最後一步,數 a 在 第 i 個位置出現了,從第 n-1 到第 i 個位置(包含 n-1i),總共有 n-i 個位置,對應着 n-i 個數。能夠獲得,前面被挑選過的數,是 n-i-1 個(總共 n-i 個減去當前即將被挑選數 a)。總共有 n 個,減去前面被挑選過的數,獲得未被挑選的個數是 n-(n-i-1)i+1。恰好就是 a,只能是 a,單獨這一步可能性就是 1/(i+1)

最後一步,是創建在前面的每一步的成立的基礎,因此得出數 a 出如今第 i 個位置的可能性是

(n-1)/n * (n-2)/(n-1) * ... * 1/(i+1)

前一項的分子和後一項的分母兩兩消去,最後獲得了 1/n

即有 n 個數,其中數 a 出如今第 i 個位置的可能性,爲 1/n。

洗牌算法隨機性獲得了證實。

參考資料


謝謝您的閱讀。

您的鼓勵和點贊,將成爲我持續學習、寫文章的動力

相關文章
相關標籤/搜索