Fisher–Yates shuffle 洗牌算法

Fisher-Yates shuffle 是一种生成有限序列的随机排列的算法——简单地说,该算法可以对序列进行混排.本人能力有限,且懒.不会扒论文去研究该算法在数学上的证明,只能抄袭网上的博客总结一遍的算法的步骤,并分析一下Lodash对该方法的简单实现.

1.原始算法步骤

Fisher–Yates shuffle 算法之所以有这个👻命名,当然是由Fisher和Yates这两个人的发明的,一开始只是用来人工混排一组数字序列,原始算法的步骤非常容易理解.
比如为了产生数字1-N之间的一组混排,可按如下步骤:

  • 写下从 1 到 N 的数字
  • 取一个从 1 到剩下的数字(包括这个数字)的随机数 k
  • 从低位开始,得到第 k 个数字(这个数字还没有被取出),把它写在独立的一个列表的最后一位
  • 重复第 2 步,直到所有的数字都被取出
  • 第 3 步写出的这个序列,现在就是原始数字的随机排列

前面说过,原始算法是用来人工混排的,如果计算机严格按照此步骤执行,第三步中从低位开始计数,加上第四部的"重复"动作决定了最坏情况下,原始方法的时间复杂度是O(n2).

2.经典的算法

对于稍微懂点编程思想的人来说,原始的算法很容易改进,因为我们不会傻到第三步真的去从低位开始数数,对于数组来说是可以直接取到的,下面我们稍微改进一下.

  • 1.给定一组待混排的有限序列P
  • 2.新初始化一个空的序列Q
  • 3.从P中随机选取一个元素
  • 4.将该元素放到序列Q的最后面,并从序列P中移除该元素
  • 重复3-4的步骤,直到序列P中元素全部选取到了序列Q中,得到的序列Q即为一组P的混排序列

算法的步骤,也符合一般人的认知,既然是洗牌嘛,每次从一个序列中随机选一个元素放到另一个序列中,得到的序列就是随机混排的嘛..这么容易想到的算法我们就称为经典算法把

简单的实现一下经典的算法

function shuffle(arr) {
  if(!Array.isArray(arr) && arr.length) {
    return []
  }
  const res = []
  for(let i = arr.length; i > 0; i --) {
    const idx = Math.floor(Math.random() * i)
    res.push(arr[idx])
    arr.splice(idx, 1)
  }
  return res
}

3.流行的算法

经典的算法貌似满足我们大多数的需求了,但是现代人精益求精,又提出了现代算法,与经典算法不同的是,现代算法在操作过程中不需要借助一个新的序列.而可以直接在当前序列中完成.算法步骤大致相同:

  • 1.给定一组待混排的有限序列P
  • 2.从P中随机选取一个未混排的元素
  • 3.将该元素与序列P的最后一个未混排的元素交换
  • 重复2-3的步骤,直到序列P中元素全部混排过

简单的实现一下现代算法吧

function shuffle (arr) {
  if(!Array.isArray(arr) && arr.length) {
    return []
  }
  for (let i = arr.length; i > 0; i--){
    const idx=  Math.floor(Math.random() * i)
    if(idx !== (i-1)) {
      const tmp = arr[idx];
      arr[idx] = arr[i-1]
      arr[i-1] = tmp
    }
  }
  return arr
}

比较经典算法和现代算法,可以发现,前者是返回新的数组,后者会改变原数组.Lodash库中Shuffle就是现代算法的实现,不同的是其交换的元素是从数组首位开始的,并且返回一个新数组

import copyArray from './.internal/copyArray.js'

function shuffle(array) {
  const length = array == null ? 0 : array.length
  if (!length) {
    return []
  }
  let index = -1
  const lastIndex = length - 1
  const result = copyArray(array)
  while (++index < length) {
    const rand = index + Math.floor(Math.random() * (lastIndex - index + 1))
    const value = result[rand]
    result[rand] = result[index]
    result[index] = value
  }
  return result
}

export default shuffle
posted @ 2018-12-25 15:12  FeMiner  阅读(3097)  评论(0编辑  收藏  举报