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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM