如何高效產生多個不重復的隨機數


《編程珠璣》有這樣一個練習題:

如果認真考慮了習題3,你將會面對生成小於n且沒有重復的k個整數的問題。最簡單的方法就是使用前k個正整數。這個極端的數據集合將不會明顯地改變位圖方法的運行時間,但是可能會歪曲系統排序的運行時間。如何生成位於0至之間的k個不同的隨機順序的隨機整數?盡量使你的程序簡短且高效。

這個題目,很多網友給出的解答是這樣的:

  1. 每產生一個,都跟前面的隨機數比較如果重復,就重新產生。但這方法不太好,且比較次數呈線性增長,越往后次數越多。
  2. 按順序產生這些數,但隨機產生它們的位置。然后選擇前面要的個數即可:
    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
}


免責聲明!

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



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