Js 数组乱序

1. 定义

数组乱序就是把数组存储值的顺序都打乱。 一般咱们在作抽奖系统或者发牌等游戏时,会遇到数组乱序的问题。 举个例子:将 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 乱序。算法

2. sort 方法

一般咱们最快想到的方法是利用 sort数组

function shuffle(arr) {
    return arr.sort(() => (Math.random() - 0.5))
}

console.log(shuffle([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))

// =>  [3, 5, 9, 10, 7, 6, 4, 8, 1, 2]
复制代码

乍一看没问题,可是运行次数多了咱们就会发现,末尾的数字为大数的几率较大,开始的数字为小数的几率较大。bash

咱们能够验证下,10000 次随机测试,10 个位置,每一个位置的平均值应该是同样的,即 (1+10)*10/2/10 = 5.5:dom

const arr = [1, 2, 3, 4, 5, 6 ,7, 8, 9, 10]
function shuffle(arr) {
  return arr.sort(() => (Math.random() - 0.5))
}

let resultArr = Array(10).fill(0)
for (let i = 0; i < 10000; i++) {
  // sort 会改变原数组,必须用新数组来进行乱序
  let newArr = [].concat(arr)
  const tmp = shuffle(newArr)
  resultArr.forEach((item, index) => {
    // 不能直接改变 item 的值, item += tmp[index], 由于 forEach 不会改变原数组
    resultArr[index] += tmp[index]
  })
}
console.log(resultArr)
const average = resultArr.map(i => i/ 10000)
console.log(average)

// => [48544, 48860, 55333, 56927, 56797, 53396, 53790, 56762, 58967, 60624]
// => [4.8544, 4.886, 5.5333, 5.6927, 5.6797, 5.3396, 5.379, 5.6762, 5.8967, 6.0624]
复制代码

能够看到,每一个位置平均值有比较明显的偏差。那么这是什么缘由呢?测试

原来,在Chrome v8引擎源码中,处理sort方法时,使用了插入排序和快速排序两种方案。当目标数组长度小于10时,使用插入排序;反之,使用快速排序和插入排序的混合排序。ui

因此用 sort 方法乱序不许确的缘由就在于:理想的方案是数组中每两个元素都要进行比较,这个比较有50%的交换位置几率。而在插入排序的算法中,当待排序元素跟有序元素进行比较时,一旦肯定了位置,就不会再跟位置前面的有序元素进行比较,因此就乱序的不完全。spa

3. 洗牌算法

能够利用洗牌算法来进行完全的乱序。 洗牌算法的思路是:code

先从数组末尾开始,选取最后一个元素,与数组中随机一个位置的元素交换位置; 而后在已经排好的最后一个元素之外的位置中,随机产生一个位置,让该位置元素与倒数第二个元素进行交换; 以此类推,打乱整个数组的顺序。排序

function shuffle(a) {
    for (let i = a.length; i; i--) {
        let j = Math.floor(Math.random() * i);
        [a[i - 1], a[j]] = [a[j], a[i - 1]];
    }
    return a;
}
复制代码

这时再测试下:游戏

const arr = [1, 2, 3, 4, 5, 6 ,7, 8, 9, 10]
function shuffle(a) {
    for (let i = a.length; i; i--) {
        let j = Math.floor(Math.random() * i);
        [a[i - 1], a[j]] = [a[j], a[i - 1]];
    }
    return a;
}

let resultArr = Array(10).fill(0)
for (let i = 0; i < 10000; i++) {
  // sort 会改变原数组,必须用新数组来进行乱序
  let newArr = [].concat(arr)
  const tmp = shuffle(newArr)
  resultArr.forEach((item, index) => {
    // 不能直接改变 item 的值, item += tmp[index], 由于 forEach 不会改变原数组
    resultArr[index] += tmp[index]
  })
}
console.log(resultArr)
const average = resultArr.map(i => i/ 10000)
console.log(average)

// => [55070, 54854, 54588, 55169, 55458, 54670, 55311, 54944, 55030, 54906]
// =>  [5.507, 5.4854, 5.4588, 5.5169, 5.5458, 5.467, 5.5311, 5.4944, 5.503, 5.4906]
复制代码

咱们能够看到,已是真正的乱序啦。

相关文章
相关标签/搜索