《編程珠璣》有這樣一個練習題:
如果認真考慮了習題3,你將會面對生成小於n且沒有重復的k個整數的問題。最簡單的方法就是使用前k個正整數。這個極端的數據集合將不會明顯地改變位圖方法的運行時間,但是可能會歪曲系統排序的運行時間。如何生成位於0至之間的k個不同的隨機順序的隨機整數?盡量使你的程序簡短且高效。
這個題目,很多網友給出的解答是這樣的:
- 每產生一個,都跟前面的隨機數比較如果重復,就重新產生。但這方法不太好,且比較次數呈線性增長,越往后次數越多。
- 按順序產生這些數,但隨機產生它們的位置。然后選擇前面要的個數即可:
var a[100] int
for i:=0; i<100; i++{
a[i]=i
}
for i:=99; i>0; i--{
index := rand.Int(100)
a[i], a[index] = a[index],a[i]
}
但是我覺得這樣效益太差,比如要從 0 到 10000000 (一千萬)中產生 500 個數,數組要交換 9999999 次,所以做了如下改進:
- a) 生成指定范圍的數組,記大小為 n,其中 n = max - min + 1 (包括范圍邊界);
- b) 用 min 到 max 的數填充數組;
- c) 隨機產生一個下標 index ,范圍 [0,n),取出數 index;
- d) 如果 index 不是最后一個數(n-1),和 n-1 數交換,縮減 n ,重復步驟 b,直到生成數需要的隨機數。
代碼實現如下:
func generate(max, min, count int) []int {
if min > max { // 交換最大最小范圍
max, min = min, max
}
if count > max-min { // 不可能,范圍大小不夠產生 count 個隨機數
return nil
}
n := max - min + 1
if n < 0 { // 范圍太大了,溢出...
return nil
}
base := make([]int, n)
for i, index := min, 0; i <= max; i++ { // 裝填數組
base[index] = i
index++
}
result := make([]int, count)
rd := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < count; i, n = i-1, n-1 {
ri := rd.Intn(n) // 隨機產生下標
result[i] = base[ri] // 取數據
if ri != n-1 { // 如果不是最后一個,和最后一個交換,好縮減 n
base[ri], base[n-1] = base[n-1], base[ri]
}
}
return result
}
