今天在作一個遊戲需求的時候碰到一個問題,問題很簡單,給定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算法平常》得到更多文章~