今天在作一个游戏需求的时候碰到一个问题,问题很简单,给定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算法平常》得到更多文章~