文章首发于: 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; }
数组中仍是有不少的学问的,看看其中的源码,也会发现更多的奥妙!