如何实现一个完全随机的数组?这并不是一个简单的问题。

    通常情况下,我们是这样做的

    1. const random = (min, max) => parseInt(Math.random() * (max - min + 1) + min, 10)
    2. const shuffle = (arr) => {
    3. for(let i in arr) {
    4. let rand = random(i, arr.length - 1)
    5. let temp = arr[i]
    6. arr[i] = arr[rand]
    7. arr[rand] = temp
    8. }
    9. return arr
    10. }

    洗牌算法的正确性原则是:产生的结果比如是有 n! 种可能,否则就是错误的。

    常见的错误是

    const random = (min, max) => parseInt(Math.random() * (max - min + 1) + min, 10)
    
    const shuffle = (arr) => {
        for(let i in arr) {
          let rand = random(0, arr.length - 1)    // 注意这里
        let temp = arr[i]
        arr[i] = arr[rand]
        arr[rand] = temp
      }
      return arr
    }
    

    如果是这样的情况下,产生的结果会有 n ^ n 种可能,而 n^n 不可能等于 n!

    我们可以利用蒙特卡罗方法验证一下洗牌算法的正确性

    洗牌算法,或者说随机乱置算法的正确性衡量标准是:对于每种可能的结果出现的概率必须相等,也就是说要足够随机

    第一种思路**,我们把数组 arr 的所有排列组合都列举出来,做成一个直方图(假设 arr = {1,2,3}):

    洗牌算法 - 图1
    正确的洗牌算法的结果是

    const random = (min, max) => {
      return parseInt(Math.random() * (max - min + 1) + min, 10)
    }
    
    const shuffle = (arr) => {
      return arr.map((item, index, array) => {
        const rand = random(index, array.length - 1)
        let temp = item
        item = array[rand]
        array[rand] = temp
        return item
      })
    }
    const map = new Map()
    let n = 1000000
    while(n--) {
      const res = shuffle([1,2,3])
      if(map.has(res.toString()))
        map.set(res.toString(), map.get(res.toString()) + 1);
      else map.set(res.toString(), 1)
    }
    
    console.log(map);
    

    image.png

    而错误的洗牌算法的结果是这样的
    image.png
    这样的话,一张牌就会被重复使用,并且生成的概率也不随机