文章首發於: https://www.xiabingbao.com/post/javascript/js-random-array.htmljavascript
在js中,能把數組隨機打亂的方法有不少,每一個方法都有本身的特色。html
這裏主要講解3個打亂數組的方法。java
這個方法的詳細操做步驟是:隨機從數組中取出一個數組放入到新數組中,而後將該數據從原數組中刪除,而後再隨機取出下一個數,直到原數據的長度爲0。git
function randomArrByOut(arr) { let result = []; let arrTemp = [...arr]; // splice會影響原數組,複製一個新的數組,防止影響原數組 while(arrTemp.length) { let index = Math.floor(Math.random() * arrTemp.length); result.push(arrTemp[index]); arrTemp.splice(index, 1); } return result; } let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; randomArrByOut(arr); // [7, 1, 3, 8, 2, 4, 6, 5, 9] randomArrByOut(arr); // [8, 4, 3, 7, 9, 2, 1, 5, 6]
這個算法看似是O(n)
的算法,但實際上arr.splice
內部是一個O(n^2)
的算法Array.prototype.splice的內部實現:外部循環用來刪除元素,內部的循環用來填充新添加的元素,或後面的元素向前移動,填充剛纔被刪除的元素的坑。總的算下來,這個算法的時間複雜度就是O(n^3)
了。github
還有一種常見的方法就是使用數組自帶的sort方法來打算數組,sort方法是直接修改當前的數組:算法
function randomSortBySort(arr) { arr.sort(() => Math.random() - 0.5); }
當前環節裏全部的測試均在Chrome中。當咱們使用9個數據,通過屢次的測試發現,打亂的數據排布並不均勻:數組
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; var n = 10000; var count = {}; while(n--) { randomSortBySort(arr); var index = arr.indexOf(1); count[index] ? count[index]++ : (count[index] = 1); } console.log(count); /* 數據1通過10000次打亂後的分佈規律,主要集中在前2個 0: 2047 1: 1403 2: 947 3: 822 4: 777 5: 822 6: 992 7: 1008 8: 1182 */
咱們再把arr的數組擴展爲15,再進行測試:dom
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; var n = 10000; var count = {}; while(n--) { randomSortBySort(arr); var index = arr.indexOf(1); count[index] ? count[index]++ : (count[index] = 1); } console.log(count); // {0: 668, 1: 647, 2: 652, 3: 665, 4: 692, 5: 652, 6: 679, 7: 657, 8: 665, 9: 683, 10: 685, 11: 690, 12: 662, 13: 663, 14: 640}
能夠發現每次打亂後的分佈比較均勻,每一個數字出如今每一個位置的機會都是均等的!post
在V8的源碼中L710行中能夠看到:測試
function InnerArraySort( array, length, comparefn ) { // In-place QuickSort algorithm. // For short (length <= 22) arrays, insertion sort is used for efficiency. // 雖然註釋是length<=22,但代碼裏是<=10 // 插入排序 var InsertionSort = function InsertionSort( a, from, to ) { }; var QuickSort = function QuickSort( a, from, to ) { var third_index = 0; while ( true ) { // Insertion sort is faster for short arrays. if ( to - from <= 10 ) { InsertionSort( a, from, to ); return; } // 快排其餘的內容 } } QuickSort(array, 0, num_non_undefined); }
sort的內部使用快速排序,當快排拆分後的分區裏的數據個數小於等於10個時,則採用插入排序!所以,當數據量比較小的時候,使用sort
打亂排序時,會形成不均等的分佈!
最後一個經典的數組打亂算法就是洗牌算法:從最後一個數據開始往前,每次從前面隨機一個位置,將二者交換,直到數組交換完畢:
function shuffleSort(arr) { var n = arr.length; while(n--) { var index = Math.floor(Math.random() * n); var temp = arr[index]; arr[index] = arr[n]; arr[n] = temp; // ES6的解耦交換方式: [arr[index], arr[n]] = [arr[n], arr[index]]; } }
這種方式是O(n)
的時間複雜度,並且還能保證一個比較均勻的分佈!高效了不少
這是從數組中隨機取出幾個元素,上面的一節是將整個數組進行排序,而這裏只是須要幾個元素而已!
固然,先把整個數組打亂了,而後再取出前n個數據也是其中的一種方法,好比咱們這裏就使用洗牌算法打亂數組,而後取出數據:
function getRandomArr(arr, num) { var _arr = arr.concat(); var n = _arr.length; // 先打亂數組 while(n--) { var index = Math.floor(Math.random() * n); [_arr[index], _arr[n]] = [_arr[n], _arr[index]]; } return _arr.slice(0, num); }
不過實際上咱們只是須要其中的幾個元素而已,若是把整個數組都打亂排序,就顯得很浪費。所以這裏咱們使用洗牌算法的思路,稍微改進一下。
從最後一個數據開始往前,每次從前面隨機一個位置,將二者交換,拿到最後的那個數據,直到達到要獲取的個數:
function getRandomArr(arr, num) { var _arr = arr.concat(); var n = _arr.length; var result = []; // 先打亂數組 while(n-- && num--) { var index = Math.floor(Math.random() * n); // 隨機位置 [_arr[index], _arr[n]] = [_arr[n], _arr[index]]; // 交換數據 result.push(_arr[n]); // 取出當前最後的值,即剛纔交換過來的值 } return result; }
數組中仍是有不少的學問的,看看其中的源碼,也會發現更多的奧妙!