大家都知道,数组的排序是一个非常重要的算法,除此之外,数组的乱序也是一种算法哦!数组的乱序也是非常有讲究的,它要求数组中的数打乱后,每个数出现在任意位置的概率相同,话不多说,直接开始试一下吧...
首先 来看一种很可能写的错误算法
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
function shuffle(arr) {
// 两个数交换的概率50%
return arr.sort(() => Math.random() - 0.5);
}
得出结果
根据数组乱序的要求,我们将函数执行10000次,查看每个位置上的平均值
let t = 10000;
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let res = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for(let i = 0; i < t; i++) {
let sorted = shuffle(arr.slice(0));
for( let i = 0; i < sorted.length; i++) {
res[i] = sorted[i] + res[i];
}
}
console.log(res.map(sum => sum / t))
这是为什么呢?问题就出在sort这个API上,对于chrome浏览器而言,当数组长度在10以内时,sort()采用插入排序,反之,则混合使用快速排序和插入排序,这样会导致选取的两个交换位置的数不随机,导致数组也就没有真正打乱。
那么,正确的乱序算法是怎样的呢?下面重点来咯: 洗牌算法 先理一下思路:
- 取数组中未洗牌的最后一个数;
- 从未洗牌的数中,随机选取一个;
- 交换两数位置;
- 重复执行1-3,直至所有数洗牌完成洗牌。
代码如下:
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
function shuffle(arr) {
let len = arr.length;
for(let i = 0; i < len; i++) {
let idx = Math.floor(Math.random() * (len - i)); //Math.floor 向下取整 Math.random [0,1)
[arr[len - 1 - i], arr[idx]] = [arr[idx], arr[len - 1 - i]]; //交换两数
}
return arr;
}
得出结果
同样,验证一下每个数出现在任意位置的概率相同是否相等呢
let t = 10000;
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let res = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for(let i = 0; i < t; i++) {
let sorted = shuffle(arr.slice(0));
for( let i = 0; i < sorted.length; i++) {
res[i] = sorted[i] + res[i];
}
}
console.log(res.map(sum => sum / t))