Knuth高效洗牌算法

今天在作一個遊戲需求的時候碰到一個問題,問題很簡單,給定75個球,編號1-75,須要保證初始化的時候位置是隨機的。python

顯然,咱們能夠初始化一個數組A,把75個數放進去,而後作一個shuffle函數隨機交換其中的元素,這樣就是隨機的。golang

我準備這樣作一個shuffle,但同時也想看看golang裏面是否有這樣的接口直接獲得結果,看了下還真有,這個函數是rand.Perm(n),這個函數會返回一個數組,好比我傳入75,會返回一個0-74的隨機數組。算法

arr := rand.Perm(75)
複製代碼

好奇心驅使我一探究竟,golang會用什麼樣的方式實現Perm函數呢?數組

打開golang的源代碼,在rand.go文件中找到這個函數:markdown

// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n).
func (r *Rand) Perm(n int) []int {
    m := make([]int, n)
    // In the following loop, the iteration when i=0 always swaps m[0] with m[0].
    // A change to remove this useless iteration is to assign 1 to i in the init
    // statement. But Perm also effects r. Making this change will affect
    // the final state of r. So this change can't be made for compatibility
    // reasons for Go 1.
    for i := 0; i < n; i++ {
        j := r.Intn(i + 1)
        m[i] = m[j]
        m[j] = i
    }
    return m
}
複製代碼

實現很簡單,然而初一看有點懵,由於沒有用到shuffle,而是一次遍歷就把事情給解決了,究竟是怎麼回事?less

仔細分析發現,這個算法很是精巧,每次遍歷都是將當前的數i和已經在數組中的隨機一個數m[j]進行交換,最終達到了公平隨機整個數組的做用。雖然只有短短3行代碼,卻讓人有種震撼的感受。dom

頓時以爲golang很NB,確實很高效。函數

上面這段代碼寫了4行的註釋,大概意思是說不能省去0那一次,看起來沒啥用處,可是爲了照顧r隨機器中的隨機序列,仍是要加上,否則可能會形成負做用,這裏面和隨機種子以及此後隨機的序列有關,爲了對隨機序列不產生影響保證公平性,不能省略0。oop

網上搜索了一下高效洗牌算法,又發現python裏面也有這樣的函數,這樣寫的:ui

for(int i = N - 1; i >= 0 ; i -- )
    swap(arr[i], arr[rand(0, i)])         // rand(0, i) 生成 [0, i] 之間的隨機整數
複製代碼

而這個算法的出處居然來自於TAOCP!算法就是大名鼎鼎的 Knuth-Shuffle,即 Knuth 洗牌算法。

看似簡單的問題,居然又扯出Knuth,我也是無語凝噎。

能把一件小事情作到極致的人,能夠稱之爲藝術家。Knuth名副其實。

最後以Knuth的一句話共勉:

A programmer who subconsciously views himself as an artist will enjoy what he does and will do it better. Donald E. Knuth 1978

關注公衆號《ACM算法平常》得到更多文章~

相關文章
相關標籤/搜索