本帖最后由 edisonx 于 2012-11-28 19:23 编辑
淺見,參考。
我們將問題重定義成較廣泛的問題。
如何產生 N 個 [LOW, UP] 之整數亂數
(其中 N <= (UP-LOW+1) ,且 (UP-LOW+1) <= 2^31 )
為了方便說明,我們將 (UP-LOW+1) 定為 RANGE。
[1] 暴力法
直接用一個 Array[N] 紀錄之前產生過的數字,每產生一個亂數再進去筆對。
暴力法產生在某些情況是適用的,通常是 (N / RANGE) 較小,甚至一半的時候考慮使用。
但若比值近似於 1 ,且 RANGE 特大時,速度就很慢了。
舉個例,產生 1000 個 [-500,500] 不重覆亂數,這時候用暴力法就特慢。
#include <array.au3>
#include <timers.au3>
const $Lower = -500
const $Upper = 500
const $N = 1000
Dim $ckBeg, $Td
Dim $rst[$N]
$ckBeg = _Timer_Init()
ArrayRnd($Lower, $Upper, $N, $rst)
$Td = _Timer_Diff($ckBeg)
_ArrayDisplay($rst, "Td = " & $Td)
; --------------------------------------------------------------------------
; Force
Func ArrayRnd(const $Low, const $Up, const $N, ByRef $Rst)
Local $rnd
Local $i, $Count=0
Local $FindFlag
While $Count < $N
$FindFlag = False
$rnd = Random($Low, $Up, 1)
; 確認是否產生過
For $i=0 To $Count-1
If $rnd = $Rst[$i] Then
$FindFlag=True ; 有產生過
ExitLoop
EndIf
Next
; 若無產生過
If Not $FindFlag Then
$Rst[$Count] = $rnd
$Count+=1
EndIf
WEnd
EndFunc
再次提醒,這種方式還是有優點的,當 RANGE 愈小,或 N / RANGE 愈小時,這種方法會是首選。
[2] 洗牌法 (shuffle)
洗牌法概念是先產生一副 [LOWER, UP] 之 poker,先拿出第 i 張牌出來,再隨機抽出第 j 張牌,將這兩張牌交換,最後再取出前面 N 張牌出來就是了。
洗牌不是洗愈多愈亂,目前筆者所知,網路上的洗牌法有以下概念 (恕以虛碼示之 )
const $Lower = -500
const $Upper = 500
const $N = 1000
Const $Range = ($Upper-$Lower+1)
Dim $Rst[$N] ; 放結果
Dim $Poker[$Range] ; Poker[i ] = i+low , for 0<=i < Range
Dim $rnd, $rnd1, $rnd2
; 法一:逐一交換, 保證每張牌都交換過
For $i = 0 To $Range-1
$rnd = Random(0, N)
Swap ( [$Poker[$rnd], Poker[$i])
Next
; 最後再將 Poker 前 N 個丟給 Rst
; 法二:隨機取兩張牌交換,進行 Range 次
For $i = 0 To $Range-1
$rnd1 = Random(0, $Range-1)
$rnd2 = Random(0, $Range-1)
Swap ( [$Poker[$rnd], Poker[$rnd2])
Next
; 最後再將 Poker 前 N 個丟給 Rst
但上述之方法都沒經過亂數均勻性測試,唯一有經過均勻性測試的是 Knuth Shuffle,
以下範例碼關鍵是 Func KnuthShuffle
#include <array.au3>
#include <timers.au3>
const $Lower = -500
const $Upper = 500
const $N = 1000
Dim $ckBeg, $Td
Dim $rst[$N]
$ckBeg = _Timer_Init()
KnuthShuffle($Lower, $Upper, $N, $rst)
$Td = _Timer_Diff($ckBeg)
_ArrayDisplay($rst, "Td = " & $Td)
Func KnuthShuffle(const $Low, const $Up, const $N, ByRef $Rst)
Local $i, $rnd, $tmp
Local $Range = ($Up-$Low+1)
Local $Poker[$Range]
; Generate a poker
For $i=0 to $Range-1
$Poker[$i] = $Low + $i
Next
; shuffle
For $i = $Range-1 to 1 step -1
$rnd = Random(0, $i)
; swap (poker, poker[rnd])
$tmp = $Poker[$i]
$Poker[$i] = $Poker[$rnd]
$Poker[$rnd] = $tmp
Next
; pick first N
For $i = 0 To $N-1
$Rst[$i] = $Poker[$i]
Next
EndFunc
但洗牌法還是有缺點,只要 N / RANGE 愈小,實際上所虛費的時間/內存空間就愈差。
[3] 適用性
(1) 注意 rand / random 產生之最大亂數值,由於 AU3 最大可產生 2^31 之亂數,故前提已當是假設。
(2) 適用於整數,浮點數 (floating) 要看情況,有些可以調整,有些不行調整。
[4] 進階議題
這次我們玩個比較特別的東西。
如何產生 500 個 [-500 500] 之整數亂數,產生結果之陣列(array,數組)裡之內容必須由小到大排好? As Soon As Possible
提示:其實用不到排序法,有用到上面洗牌法概念,有興趣的可稍想一下。
|