1. 程式人生 > >錯誤的洗牌演算法

錯誤的洗牌演算法

最近看到一篇文章,上面介紹了12種JavaScript技巧,其中最後一種介紹了陣列元素的洗牌,程式碼很簡單

var list = [1,2,3];
console.log(list.sort(function() { Math.random() - 0.5 })); // [2,1,3]

乍一看覺得這段程式碼很精髓,簡單明瞭又不乏智慧,興奮的我甚至還記在了本子上。直到我看到了底下面一行評論,說這段程式碼has a mistake。處於好奇,我就點了他附帶的連結

這篇文章毫不客氣地提出觀點:以上程式碼看似巧妙利用了 Array.prototype.sort 實現隨機,但是,卻有非常嚴重的問題,甚至是完全錯誤

我當時就震了個驚啊~然後不服氣的把它的測試程式碼拿來寫了個測試用例:

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="shuffle.js"></script>
    </head>
    <body>
        洗牌演算法
    </body>
    <script>
        var
array = [0,1,2,3,4,5,6,7,8,9]; var result = [0,0,0,0,0,0,0,0,0,0]; var times = 1000000; for (var i = 0; i < times; i++) { var sorted = shuffle(array.slice(0)); //arr如果不用copy的話,上一次洗牌的值會覆蓋當前的arr。 sorted.forEach(function(val,i){ result[i]+=val; }); } result = result.map(function
(val){
return val/t; }); console.log(result);
</script> </html>

var sorted = shuffle(array.slice(0));關於這一行我一開始還很疑惑,幹嘛要slice一下,直接用array的結果明明就是正確的。如圖1。
圖一
我就覺得肯定是樓主裝逼寫的這篇文章。

然後接著看他的其他分析,他用js實現了3種排序演算法,冒泡,插入和快速排序。其中冒泡和插入的時間複雜度相同,而插入排序比較的次數要相對少一點。我分別運行了這些程式碼,發現結果真的如他分析的規律一樣:

  • 冒泡的後面比前面大:
    這裡寫圖片描述
  • 插入的前面比後面大:
    這裡寫圖片描述
  • 快速的則沒有什麼規律:
    這裡寫圖片描述

注:以上結果都是基於arry.slice(0)的

然後我就開始質疑自己開始的判斷了,把這些排序都去了,改成arry自帶的sort方法,然後打斷點除錯,發現如果不用用arry.slice(0)的話,每次shuffle後,array物件都會被改變,這樣原始的順序[0~9]的順序就被打亂了,這樣就真的相當於隨機洗牌了。難怪圖1的結果是正確的。
但是,我要測的是演算法啊,總不能還依賴於使用者怎麼呼叫演算法啊。要是別人不用array,那不是咖哩給給了。。

最後,我又測試了一下他寫的那個 經典的隨機排列,程式碼貼一下,加深自己記憶:

function shuffle(arr){
    var len = arr.length;
    for(var i =0 ;i <len -1 ;i++){
        var index = Math.floor(Math.random()*(len-i));
        /*
         *從前 len - i 個元素裡隨機一個位置,將這個元素和第 len - i 個元素進行交換
         */
        var temp = arr[index];
        arr[index] = arr[len-i-1];
        arr[len-i-1] = temp;
    }
    return arr;
}

最後他給的隨機性的數學歸納法證明徹底征服了我,有興趣可以檢視原文。然後我表示我是跪著寫完這篇部落格的。。


總結

這個演算法的確是徹底錯誤的。
看到別人的程式碼,不要盲目崇拜,要多想多質疑。這樣才能提高啊。。