錯誤的洗牌演算法
最近看到一篇文章,上面介紹了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;
}
最後他給的隨機性的數學歸納法證明徹底征服了我,有興趣可以檢視原文。然後我表示我是跪著寫完這篇部落格的。。
總結
這個演算法的確是徹底錯誤的。
看到別人的程式碼,不要盲目崇拜,要多想多質疑。這樣才能提高啊。。