關於JavaScript的數組隨機排序

之前再做數組的隨機排序問題,潛意識想到的第一個方法就是 產生隨機下標排序。曾經網上一直流傳着這樣一個寫法:

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

之前一直覺得這種方法簡單,但是後來總結時,再思考, 每個元素仍然有很大機率在它原來的位置附近出現,他好像不是真正的隨機排序。

探索

看了一下ECMAScript中關於Array.prototype.sort(comparefn)的標準,其中並沒有規定具體的實現算法,但是提到一點:

Calling comparefn(a,b) always returns the same value v when given a
specific pair of values a and b as its two arguments.

也就是說,對同一組a、b的值,comparefn(a, b)需要總是返回相同的值。而上面的() => Math.random() -0.5(a, b) => Math.random() - 0.5顯然不滿足這個條件。

翻看v8引擎數組部分的源碼,注意到它出於對性能的考慮,對短數組使用的是插入排序,對長數組則使用了快速排序。至此,也就能理解爲什麼() => Math.random() - 0.5並不能真正隨機打亂數組排序了。

(源碼中說的是對長度小於等於 22 的使用插入排序,大於 22 的使用快排,但實際測試結果顯示分界長度是 10。)

解決方案

既然(a, b) => Math.random() - 0.5的問題是不能保證針對同一組a、b每次返回的值相同,那麼我們不妨將數組元素改造一下,比如將每個元素i改造爲:

let new_i = { 
    v: i, 
    r: Math.random() 
}; 

完整代碼:

function shuffle(arr) { 
    //將原數組改爲對象數組(值、隨機編號 爲對象的兩個屬性)
    let new_arr = arr.map(i => ({v: i, r: Math.random()})); 
   //將對象數組 按照隨機編號進行排序
    new_arr.sort((a, b) => a.r - b.r); 
    //將數組提取出v值,插入到原數組中
    arr.splice(0, arr.length, ...new_arr.map(i => i.v)); 
} 

let a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']; 
let n = 10000; 
let count = (new Array(a.length)).fill(0); 

for (let i = 0; i < n; i ++) {  
    shuffle(a); 
    count[a.indexOf('a')]++;  
} 
console.log(count); 

多次驗證,這個方法足夠隨機了。但在性能上並不是很好,需要遍歷幾次數組,還要對數組進行splice等操作。

方法二: (Fisher–Yates shuffle費雪耶茲隨機置亂算法) !!!推薦

考察Lodash 庫中的 shuffle 算法,注意到它使用的實際上是Fisher–Yates 洗牌算法。

算法思想:從0~i(i的變化爲 n-1到0遞減)中隨機取得一個下標,和最後一個元素(i)交換。

function shuffle(arr) { 
  var i = arr.length, t, j; 
  while (i) { 
    j = Math.floor(Math.random() * i--); //!!!
    t = arr[i]; 
    arr[i] = arr[j]; 
    arr[j] = t; 
  } 
} 

es6版本:

function shuffle(arr) { 
    let i = arr.length; 
    while (i) { 
        let j = Math.floor(Math.random() * i--); 
        [arr[j], arr[i]] = [arr[i], arr[j]]; 
    } 
} 

算法需要的時間正比於要隨機置亂的數,不需要額爲的存儲空間開銷。

小結:
如果要將數組隨機排序,千萬不要再用(a, b) => Math.random() - 0.5這樣的方法。目前而言,Fisher–Yates shuffle 算法應該是最好的選擇。

參考鏈接:
http://developer.51cto.com/art/201704/536457.htm
http://blog.csdn.net/lhkaikai/article/details/25627161

發佈了171 篇原創文章 · 獲贊 44 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章