关于JavaScript的数组随机排序
2017-08-24 23:08
316 查看
之前再做数组的随机排序问题,潜意识想到的第一个方法就是 产生随机下标排序。曾经网上一直流传着这样一个写法:
之前一直觉得这种方法简单,但是后来总结时,再思考, 每个元素仍然有很大机率在它原来的位置附近出现,他好像不是真正的随机排序。
探索
看了一下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)需要总是返回相同的值。而上面的
翻看v8引擎数组部分的源码,注意到它出于对性能的考虑,对短数组使用的是插入排序,对长数组则使用了快速排序。至此,也就能理解为什么() => Math.random() - 0.5并不能真正随机打乱数组排序了。
(源码中说的是对长度小于等于 22 的使用插入排序,大于 22 的使用快排,但实际测试结果显示分界长度是 10。)
解决方案
既然(a, b) => Math.random() - 0.5的问题是不能保证针对同一组a、b每次返回的值相同,那么我们不妨将数组元素改造一下,比如将每个元素i改造为:
完整代码:
多次验证,这个方法足够随机了。但在性能上并不是很好,需要遍历几次数组,还要对数组进行splice等操作。
方法二: (Fisher–Yates shuffle费雪耶兹随机置乱算法) !!!推荐
考察Lodash 库中的 shuffle 算法,注意到它使用的实际上是Fisher–Yates 洗牌算法。
算法思想:从0~i(i的变化为 n-1到0递减)中随机取得一个下标,和最后一个元素(i)交换。
es6版本:
算法需要的时间正比于要随机置乱的数,不需要额为的存储空间开销。
小结:
如果要将数组随机排序,千万不要再用(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
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
相关文章推荐
- 关于 JavaScript 的数组随机排序
- 分享两个JavaScript打乱数组顺序实现随机排序洗牌的方法(应用于音乐视频的随机播放等)
- javascript 数组随机排序
- JavaScript数组随机排序函数
- JavaScript中关于数组的排序方法解读
- JavaScript实现数组随机排序的方法
- JavaScript实现数组随机排序的方法
- 【JavaScript】数组随机排序
- JavaScript-如何实现数组的随机排序?
- javascript数组随机排序实例分析
- JavaScript学习笔记之数组随机排序
- JavaScript学习笔记之数组随机排序
- js 数组随机排序
- javascript中对数组中的数字进行排序的办法
- javascript的数组排序
- 对JavaScript对象数组按指定属性和排序方向进行排序
- JavaScript数值数组排序示例分享
- JavaScript像数组添加元素并排序
- JavaScript对象数组的排序处理方法