Js 數組亂序

1. 定義

數組亂序就是把數組存儲值的順序都打亂。 一般咱們在作抽獎系統或者發牌等遊戲時,會遇到數組亂序的問題。 舉個例子:將 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 亂序。算法

2. sort 方法

一般咱們最快想到的方法是利用 sort數組

function shuffle(arr) {
    return arr.sort(() => (Math.random() - 0.5))
}

console.log(shuffle([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))

// =>  [3, 5, 9, 10, 7, 6, 4, 8, 1, 2]
複製代碼

乍一看沒問題,可是運行次數多了咱們就會發現,末尾的數字爲大數的機率較大,開始的數字爲小數的機率較大。bash

咱們能夠驗證下,10000 次隨機測試,10 個位置,每一個位置的平均值應該是同樣的,即 (1+10)*10/2/10 = 5.5:dom

const arr = [1, 2, 3, 4, 5, 6 ,7, 8, 9, 10]
function shuffle(arr) {
  return arr.sort(() => (Math.random() - 0.5))
}

let resultArr = Array(10).fill(0)
for (let i = 0; i < 10000; i++) {
  // sort 會改變原數組,必須用新數組來進行亂序
  let newArr = [].concat(arr)
  const tmp = shuffle(newArr)
  resultArr.forEach((item, index) => {
    // 不能直接改變 item 的值, item += tmp[index], 由於 forEach 不會改變原數組
    resultArr[index] += tmp[index]
  })
}
console.log(resultArr)
const average = resultArr.map(i => i/ 10000)
console.log(average)

// => [48544, 48860, 55333, 56927, 56797, 53396, 53790, 56762, 58967, 60624]
// => [4.8544, 4.886, 5.5333, 5.6927, 5.6797, 5.3396, 5.379, 5.6762, 5.8967, 6.0624]
複製代碼

能夠看到,每一個位置平均值有比較明顯的偏差。那麼這是什麼緣由呢?測試

原來,在Chrome v8引擎源碼中,處理sort方法時,使用了插入排序和快速排序兩種方案。當目標數組長度小於10時,使用插入排序;反之,使用快速排序和插入排序的混合排序。ui

因此用 sort 方法亂序不許確的緣由就在於:理想的方案是數組中每兩個元素都要進行比較,這個比較有50%的交換位置機率。而在插入排序的算法中,當待排序元素跟有序元素進行比較時,一旦肯定了位置,就不會再跟位置前面的有序元素進行比較,因此就亂序的不完全。spa

3. 洗牌算法

能夠利用洗牌算法來進行完全的亂序。 洗牌算法的思路是:code

先從數組末尾開始,選取最後一個元素,與數組中隨機一個位置的元素交換位置; 而後在已經排好的最後一個元素之外的位置中,隨機產生一個位置,讓該位置元素與倒數第二個元素進行交換; 以此類推,打亂整個數組的順序。排序

function shuffle(a) {
    for (let i = a.length; i; i--) {
        let j = Math.floor(Math.random() * i);
        [a[i - 1], a[j]] = [a[j], a[i - 1]];
    }
    return a;
}
複製代碼

這時再測試下:遊戲

const arr = [1, 2, 3, 4, 5, 6 ,7, 8, 9, 10]
function shuffle(a) {
    for (let i = a.length; i; i--) {
        let j = Math.floor(Math.random() * i);
        [a[i - 1], a[j]] = [a[j], a[i - 1]];
    }
    return a;
}

let resultArr = Array(10).fill(0)
for (let i = 0; i < 10000; i++) {
  // sort 會改變原數組,必須用新數組來進行亂序
  let newArr = [].concat(arr)
  const tmp = shuffle(newArr)
  resultArr.forEach((item, index) => {
    // 不能直接改變 item 的值, item += tmp[index], 由於 forEach 不會改變原數組
    resultArr[index] += tmp[index]
  })
}
console.log(resultArr)
const average = resultArr.map(i => i/ 10000)
console.log(average)

// => [55070, 54854, 54588, 55169, 55458, 54670, 55311, 54944, 55030, 54906]
// =>  [5.507, 5.4854, 5.4588, 5.5169, 5.5458, 5.467, 5.5311, 5.4944, 5.503, 5.4906]
複製代碼

咱們能夠看到,已是真正的亂序啦。

相關文章
相關標籤/搜索